"""
Variable resolver for prompt templates.
[AC-AISVC-56] Built-in and custom variable replacement engine.
"""

import logging
import re
from datetime import datetime
from typing import Any

from app.core.middleware import PROMPT_PROTECTED_VARIABLES

logger = logging.getLogger(__name__)

VARIABLE_PATTERN = re.compile(r"\{\{(\w+)\}\}")

BUILTIN_VARIABLES = {
    "persona_name": "小N",
    "current_time": lambda: datetime.now().strftime("%Y-%m-%d %H:%M"),
    "channel_type": "default",
    "tenant_name": "平台",
    "session_id": "",
    "available_tools": "",
    "query": "",
    "history": "",
    "internal_protocol": "",
    "output_contract": "",
}


class VariableResolver:
    """
    [AC-AISVC-56] Variable replacement engine for prompt templates.

    Supports:
    - Built-in variables: persona_name, current_time, channel_type, tenant_name, session_id
    - Custom variables: defined in template with defaults
    """

    def __init__(
        self,
        channel_type: str = "default",
        tenant_name: str = "平台",
        session_id: str = "",
    ):
        self._context = {
            "channel_type": channel_type,
            "tenant_name": tenant_name,
            "session_id": session_id,
        }

    def resolve(
        self,
        template: str,
        variables: list[dict[str, Any]] | None = None,
        extra_context: dict[str, Any] | None = None,
    ) -> str:
        """
        Resolve all {{variable}} placeholders in the template.

        Args:
            template: Template string with {{variable}} placeholders
            variables: Custom variable definitions from template
            extra_context: Additional context for resolution

        Returns:
            Template with all variables replaced
        """
        context = self._build_context(variables, extra_context)

        def replace_var(match: re.Match) -> str:
            var_name = match.group(1)
            if var_name in context:
                value = context[var_name]
                if callable(value):
                    return str(value())
                return str(value)
            logger.warning(f"Unknown variable in template: {var_name}")
            return match.group(0)

        resolved = VARIABLE_PATTERN.sub(replace_var, template)
        return resolved

    def _build_context(
        self,
        variables: list[dict[str, Any]] | None,
        extra_context: dict[str, Any] | None,
    ) -> dict[str, Any]:
        """Build the complete context for variable resolution."""
        context = {}

        for key, value in BUILTIN_VARIABLES.items():
            if key in self._context:
                context[key] = self._context[key]
            else:
                context[key] = value

        if variables:
            for var in variables:
                name = var.get("name")
                default = var.get("default", "")
                if not name:
                    continue
                if name in PROMPT_PROTECTED_VARIABLES:
                    logger.warning(
                        "Protected prompt variable '%s' cannot be overridden by template defaults",
                        name,
                    )
                    continue
                context[name] = default

        if extra_context:
            # Runtime/system context has the highest priority, including protected vars.
            context.update(extra_context)

        return context

    def extract_variables(self, template: str) -> list[str]:
        """
        Extract all variable names from a template.

        Args:
            template: Template string

        Returns:
            List of variable names found in the template
        """
        return VARIABLE_PATTERN.findall(template)

    def validate_variables(
        self,
        template: str,
        defined_variables: list[dict[str, Any]] | None,
    ) -> dict[str, Any]:
        """
        Validate that all variables in template are defined.

        Args:
            template: Template string
            defined_variables: Variables defined in template metadata

        Returns:
            Dict with 'valid' boolean and 'missing' list
        """
        used_vars = set(self.extract_variables(template))
        builtin_vars = set(BUILTIN_VARIABLES.keys())

        defined_names = set()
        if defined_variables:
            defined_names = {v.get("name") for v in defined_variables if v.get("name")}

        available_vars = builtin_vars | defined_names
        missing = used_vars - available_vars

        return {
            "valid": len(missing) == 0,
            "missing": list(missing),
            "used_variables": list(used_vars),
        }
