"""
LLM-as-judge: calls an LLM to evaluate output quality.

Provider-agnostic — supports OpenAI and Anthropic via lazy imports.
Requires the [eval] extras: pip install 'llmhq-releaseops[eval]'
"""

from __future__ import annotations

import json
import logging
from typing import Any, Dict, Optional

from llmhq_releaseops.models.eval_result import AssertionResult
from llmhq_releaseops.models.eval_suite import Assertion

logger = logging.getLogger(__name__)

JUDGE_PROMPT = """You are an evaluation judge. Evaluate the following LLM output against the given criteria.

## Rubric
{rubric}

## Input Context
{context}

## Expected Output (if provided)
{expected}

## Actual Output
{output}

## Instructions
Evaluate the output against the rubric. Respond with a JSON object:
{{"passed": true, "confidence": 0.95, "reasoning": "brief explanation"}}

Respond ONLY with the JSON object, no other text."""


class LLMJudge:
    """
    Calls an LLM to evaluate output quality.

    Config keys:
        model: str (e.g., "gpt-4-turbo", "claude-3-haiku-20240307")
        rubric: str (evaluation criteria)
        provider: str (optional, inferred from model name)
        temperature: float (default 0.0 for deterministic judging)
    """

    def __init__(self) -> None:
        self._clients: Dict[str, Any] = {}

    def evaluate(
        self,
        output: str,
        assertion: Assertion,
        expected: Optional[str] = None,
        context: Optional[Dict[str, Any]] = None,
    ) -> AssertionResult:
        model = assertion.config.get("model", "gpt-4-turbo")
        rubric = assertion.config.get("rubric", "Evaluate quality and correctness.")
        provider = assertion.config.get("provider")
        temperature = assertion.config.get("temperature", 0.0)

        prompt = JUDGE_PROMPT.format(
            rubric=rubric,
            context=json.dumps(context) if context else "N/A",
            expected=expected or "N/A",
            output=output,
        )

        try:
            raw_response = self._call_llm(provider, model, prompt, temperature)
            result = self._parse_judge_response(raw_response)
        except ImportError as e:
            return AssertionResult(
                judge_type="llm_judge",
                passed=False,
                confidence=0.0,
                reasoning=str(e),
                weight=assertion.weight,
            )
        except Exception as e:
            logger.error(f"LLM judge error: {e}")
            return AssertionResult(
                judge_type="llm_judge",
                passed=False,
                confidence=0.0,
                reasoning=f"LLM judge error: {e}",
                weight=assertion.weight,
            )

        return AssertionResult(
            judge_type="llm_judge",
            passed=result["passed"],
            confidence=result["confidence"],
            reasoning=result["reasoning"],
            weight=assertion.weight,
            details={"model": model, "rubric": rubric},
        )

    def _call_llm(
        self, provider: Optional[str], model: str, prompt: str, temperature: float
    ) -> str:
        """Call LLM API. Lazily imports and caches clients."""
        if provider == "openai" or (not provider and model.startswith("gpt")):
            return self._call_openai(model, prompt, temperature)
        elif provider == "anthropic" or (not provider and model.startswith("claude")):
            return self._call_anthropic(model, prompt, temperature)
        else:
            raise ValueError(f"Unsupported LLM provider: {provider or 'unknown'}")

    def _call_openai(self, model: str, prompt: str, temperature: float) -> str:
        try:
            from openai import OpenAI
        except ImportError:
            raise ImportError(
                "openai package required for LLM judge. "
                "Install with: pip install 'llmhq-releaseops[eval]'"
            )
        if "openai" not in self._clients:
            self._clients["openai"] = OpenAI()
        client = self._clients["openai"]
        response = client.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=temperature,
        )
        return response.choices[0].message.content

    def _call_anthropic(self, model: str, prompt: str, temperature: float) -> str:
        try:
            from anthropic import Anthropic
        except ImportError:
            raise ImportError(
                "anthropic package required for LLM judge. "
                "Install with: pip install 'llmhq-releaseops[eval]'"
            )
        if "anthropic" not in self._clients:
            self._clients["anthropic"] = Anthropic()
        client = self._clients["anthropic"]
        response = client.messages.create(
            model=model,
            max_tokens=512,
            temperature=temperature,
            messages=[{"role": "user", "content": prompt}],
        )
        return response.content[0].text

    @staticmethod
    def _parse_judge_response(text: str) -> Dict[str, Any]:
        """Parse the JSON response from the judge LLM."""
        cleaned = text.strip()
        # Strip markdown code fences if present
        if cleaned.startswith("```"):
            lines = cleaned.split("\n")
            lines = [l for l in lines if not l.strip().startswith("```")]
            cleaned = "\n".join(lines).strip()

        try:
            data = json.loads(cleaned)
            return {
                "passed": bool(data.get("passed", False)),
                "confidence": float(data.get("confidence", 0.5)),
                "reasoning": str(data.get("reasoning", "")),
            }
        except (json.JSONDecodeError, ValueError):
            return {
                "passed": False,
                "confidence": 0.0,
                "reasoning": f"Failed to parse judge response: {text[:200]}",
            }
