"""Helper that prepares prompts and parses structured LLM responses."""

from __future__ import annotations

import os
from typing import Any, Dict, List, Optional, Protocol, Tuple, Union, runtime_checkable

from pydantic import BaseModel, Field, ValidationError, confloat


@runtime_checkable
class LLMClient(Protocol):
    """Protocol for LLM clients that support JSON chat responses."""

    def chat_json(
        self, messages: List[Dict[str, Any]], retry_strict: bool = True
    ) -> Dict[str, Any]:
        """Call the LLM API expecting a JSON response."""
        ...


class LLMDecision(BaseModel):
    """Structured recommendation produced by the LLM assistant."""

    decision: str  # approve | reject | unsure
    confidence: confloat(ge=0.0, le=1.0)
    suggested_domain: Optional[str] = None
    reasoning: str = Field(default="")


PROMPT_TEMPLATE = """You are reviewing a company promotion candidate.

Company: {company_name}
List: {list_source} {list_year}, Rank {rank}
Wikidata: {qid}
Ticker: {ticker}
Expected Country: {country}
Candidate website(s): {candidate_domains}
Signals: {signals_json}
Conflicts: {conflicts_json}

Decision guidance:
- Strong authority signals (authority_via_lei=true or authority_match=true) + domain_verified=true + country aligned -> approve even if ticker is missing.
- If signals are weak AND conflicts imply parent/subsidiary mismatch, lean unsure.
- Prefer the best-fit domain if multiple; return null only when no plausible site exists.

Follow the examples exactly:
1) Apple Inc. (Q312), ticker AAPL -> {{"decision":"approve","suggested_domain":"apple.com","confidence":0.95,"reasoning":"SEC match + canonical site"}}
2) Contoso SA, no QID, country mismatch -> {{"decision":"unsure","suggested_domain":null,"confidence":0.40,"reasoning":"Insufficient authority or country alignment"}}
3) LEI-verified subsidiary with domain and country aligned, ticker missing -> {{"decision":"approve","suggested_domain":"example.com","confidence":0.80,"reasoning":"Authority via LEI + verified domain + country aligned"}}

Now review this candidate and respond ONLY with JSON:
{{"decision":"approve|reject|unsure","suggested_domain":"example.com|null","confidence":0.00,"reasoning":"..."}}"""


class PromotionLLMAssistant:
    """Compose prompts for LLM review and validate the reply.

    Accepts any LLMClient implementation (OllamaClient, GeminiClient, etc.)
    """

    def __init__(self, client: LLMClient) -> None:
        self.client = client

    def review(
        self,
        *,
        company_name: str,
        list_source: str,
        list_year: Union[int, str],
        rank: Optional[Union[int, str]],
        qid: Optional[str],
        ticker: Optional[str],
        country: Optional[str],
        candidate_domains: List[str],
        signals_json: str,
        conflicts_json: str,
    ) -> Tuple[Optional[LLMDecision], Dict[str, object]]:
        message = PROMPT_TEMPLATE.format(
            company_name=company_name,
            list_source=list_source,
            list_year=list_year,
            rank=rank if rank is not None else "MISSING",
            qid=qid or "MISSING",
            ticker=ticker or "MISSING",
            country=country or "MISSING",
            candidate_domains=", ".join(candidate_domains)
            if candidate_domains
            else "MISSING",
            signals_json=signals_json,
            conflicts_json=conflicts_json,
        )

        raw = self.client.chat_json([{"role": "user", "content": message}])
        if raw.get("_parse_error"):
            return None, raw

        if "decision" in raw:
            raw["decision"] = self._normalize_decision(raw["decision"])

        if "confidence" in raw and isinstance(raw["confidence"], str):
            try:
                raw["confidence"] = float(raw["confidence"])
            except (ValueError, TypeError):
                raw["confidence"] = 0.0

        if "suggested_domain" in raw:
            domain = raw["suggested_domain"]
            if isinstance(domain, str):
                cleaned = domain.strip()
                if cleaned.lower() in {"", "null", "none"}:
                    raw["suggested_domain"] = None
                else:
                    raw["suggested_domain"] = cleaned.lower()

        try:
            decision = LLMDecision.model_validate(raw)
            if (
                decision.decision == "reject"
                and float(decision.confidence or 0.0) < 0.2
            ):
                decision.decision = "unsure"
            return decision, raw
        except ValidationError as exc:
            return None, {**raw, "_validation_error": str(exc)}

    @staticmethod
    def _normalize_decision(value: object) -> str:
        if not isinstance(value, str):
            return "unsure"
        normalized = value.strip().lower()
        mapping = {
            "yes": "approve",
            "approved": "approve",
            "accept": "approve",
            "no": "reject",
            "rejected": "reject",
            "deny": "reject",
            "denied": "reject",
            "unsure": "unsure",
            "unknown": "unsure",
            "uncertain": "unsure",
            "unclear": "unsure",
        }
        return mapping.get(normalized, normalized)


def create_llm_client(backend: Optional[str] = None) -> LLMClient:
    """Factory function to create an LLM client based on configuration.

    Args:
        backend: "gemini" or "ollama" (deprecated). Defaults to PROMOTION_LLM_BACKEND env var,
                 or "gemini" if not set.

    Returns:
        LLMClient instance (GeminiClient or OllamaClient)

    Environment variables:
        PROMOTION_LLM_BACKEND: "gemini" (default) or "ollama" (deprecated)
        GEMINI_MODEL: Gemini model name (default: "gemini-3-flash-preview")
        OLLAMA_HOST: Ollama server URL (default: "http://localhost:11434") - deprecated
        OLLAMA_MODEL: Ollama model name (default: "qwen2.5:14b") - deprecated
    """
    import logging
    logger = logging.getLogger(__name__)

    backend = backend or os.getenv("PROMOTION_LLM_BACKEND", "gemini")

    if backend.lower() == "gemini":
        from .gemini_client import GeminiClient

        model = os.getenv("GEMINI_MODEL") or os.getenv("VERTEX_MODEL") or "gemini-3-flash-preview"
        return GeminiClient(model=model, timeout_s=30)

    elif backend.lower() == "ollama":
        logger.warning("Ollama backend is deprecated. Migrate to Gemini.")
        from .ollama_client import OllamaClient

        host = os.getenv("OLLAMA_HOST", "http://localhost:11434")
        model = os.getenv("OLLAMA_MODEL", "qwen2.5:14b")
        return OllamaClient(host=host, model=model, timeout_s=30)

    else:
        raise ValueError(f"Unknown LLM backend: {backend}. Use 'gemini' or 'ollama'.")
