"""YAML loading, template processing, and environment variable interpolation.

This module handles all YAML parsing, Jinja2 template preprocessing,
and ``${ENV_VAR}`` interpolation for actor configuration files.  It is
extracted from ``config.py`` to keep that module under the 500-line limit.
"""

from __future__ import annotations

import json
import os
import re
from typing import Any, cast

from cleveragents.actor.yaml_template_engine import YAMLTemplateEngine

try:  # Optional dependency; present in dev/test extras
    import yaml
except Exception:  # pragma: no cover - defensive optional import
    yaml = None


def load_yaml_text(text: str) -> dict[str, Any]:
    """Parse YAML or JSON text into a configuration blob.

    Args:
        text: Raw YAML or JSON string.

    Returns:
        Parsed configuration dictionary.
    """
    try:
        data = json.loads(text)
    except json.JSONDecodeError:
        data = _load_yaml_content(text)
    else:
        if data is None:
            return {}
        if not isinstance(data, dict):
            raise ValueError("Config must be a JSON or YAML object")
        return cast(dict[str, Any], data)
    return data


def _load_yaml_content(text: str) -> dict[str, Any]:
    """Parse YAML text with Jinja2 template support and env var interpolation."""
    if yaml is None:
        raise ValueError("PyYAML is required for YAML actor configs")

    raw: Any
    try:
        if "{%" in text or "{{" in text:
            protected = re.sub(r"{{", "<<<TEMPLATE_START>>>", text)
            protected = re.sub(r"}}", "<<<TEMPLATE_END>>>", protected)
            protected = re.sub(r"{%", "<<<BLOCK_START>>>", protected)
            protected = re.sub(r"%}", "<<<BLOCK_END>>>", protected)

            engine = YAMLTemplateEngine()
            minimal_context: dict[str, dict[str, Any]] = {
                "context": {
                    "paper_details": {
                        "topic": "",
                        "length": "",
                        "audience": "",
                        "publication": "",
                        "format": "",
                        "other": "",
                    },
                    "brainstorming_summary": "",
                    "vetting_sources": list[str](),
                    "table_of_contents": "",
                    "deep_research_sources": "",
                    "current_section_to_write": "",
                    "paper_content": dict[str, Any](),
                    "final_paper_text": "",
                    "proofread_paper": "",
                }
            }
            raw = engine.load_string(protected, context=minimal_context)
            raw = _restore_template_syntax(raw)
        else:
            raw = yaml.safe_load(text)
    except Exception as exc:  # pragma: no cover - defensive
        raise ValueError(f"Failed to parse config: {exc}") from exc

    if raw is None:
        raw = {}
    if not isinstance(raw, dict):
        raise ValueError("Config must be a JSON or YAML object")

    interpolated = interpolate_env_vars(raw)
    if not isinstance(interpolated, dict):
        raise ValueError("Config must be a JSON or YAML object")
    return cast(dict[str, Any], interpolated)


def _restore_template_syntax(config: Any) -> Any:
    """Restore Jinja2 template syntax after YAML parsing."""
    if isinstance(config, dict):
        config_dict = cast(dict[str, Any], config)
        restored: dict[str, Any] = {}
        for key, value in config_dict.items():
            if key == "system_prompt" and isinstance(value, str):
                updated = value.replace("<<<TEMPLATE_START>>>", "{{").replace(
                    "<<<TEMPLATE_END>>>", "}}"
                )
                updated = updated.replace("<<<BLOCK_START>>>", "{%")
                updated = updated.replace("<<<BLOCK_END>>>", "%}")
                restored[key] = updated
            else:
                restored[key] = _restore_template_syntax(value)
        return restored
    if isinstance(config, list):
        config_list = cast(list[Any], config)
        return [_restore_template_syntax(item) for item in config_list]
    return config


def interpolate_env_vars(config: Any) -> Any:
    """Recursively interpolate ``${ENV_VAR}`` and ``${ENV_VAR:default}`` patterns.

    Supports type coercion for boolean, integer, and float defaults.
    """
    if isinstance(config, dict):
        config_dict = cast(dict[str, Any], config)
        return {k: interpolate_env_vars(v) for k, v in config_dict.items()}
    if isinstance(config, list):
        config_list = cast(list[Any], config)
        return [interpolate_env_vars(i) for i in config_list]
    if isinstance(config, str):
        env_var_pattern = r"\${([A-Za-z0-9_]+)(?::([^}]*))?\}"

        def replace_env_var(match: re.Match[str]) -> str:
            env_var = match.group(1)
            default_value = match.group(2) if match.group(2) is not None else None

            env_value = os.environ.get(env_var)
            if env_value is None:
                if default_value is not None:
                    if default_value.lower() in {"true", "false"}:
                        return str(default_value.lower() == "true")
                    if default_value.lstrip("-").isdigit():
                        return default_value
                    return default_value
                raise ValueError(f"Environment variable '{env_var}' is not set")
            return env_value

        substituted = re.sub(env_var_pattern, replace_env_var, config)

        lowered = substituted.lower()
        if lowered in {"true", "false"}:
            return lowered == "true"
        if substituted.lstrip("-").isdigit():
            return int(substituted)
        if (
            substituted.lstrip("-").replace(".", "", 1).isdigit()
            and substituted.count(".") == 1
        ):
            return float(substituted)
        return substituted
    return config
