"""
Judge LLM integration for scoring generated code.

Uses an independent LLM (Google Gemini) to evaluate code quality,
detect hallucinations, and score correctness. Using a separate model
from the coding LLM avoids self-grading bias.
"""

from __future__ import annotations

import json
import logging
import re

import httpx

from .models import Judgement

logger = logging.getLogger("clew.bench.judge")

JUDGE_PROMPT_TEMPLATE = """\
You are a Senior Staff Engineer reviewing a Junior Developer's code submission.

TASK: {instruction}
SUCCESS CRITERIA: {success_criteria}

SUBMITTED CODE:
```
{code}
```

Evaluate this solution and respond with ONLY a JSON object (no markdown fences):
{{
    "correctness": <int 1-5, does it meet the success criteria?>,
    "efficiency": <int 1-5, is it clean, idiomatic, and maintainable?>,
    "hallucinations": [<list: imports, calls, or references to nonexistent things>],
    "comments": "<brief explanation of your scoring>"
}}
"""


def _parse_judgement_json(text: str) -> dict:
    """Extract JSON from judge response, handling markdown fences."""
    # Try direct parse first
    text = text.strip()
    # Strip markdown code fences if present
    if text.startswith("```"):
        text = re.sub(r"^```(?:json)?\s*\n?", "", text)
        text = re.sub(r"\n?```\s*$", "", text)
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        # Try to find JSON object in the response
        match = re.search(r"\{[^{}]*\}", text, re.DOTALL)
        if match:
            return json.loads(match.group())
        raise


class JudgeLLM:
    """Evaluates generated code using Google Gemini."""

    def __init__(self, api_key: str, model: str = "gemini-1.5-pro"):
        self.api_key = api_key
        self.model = model
        self._client = httpx.AsyncClient(timeout=60.0)

    async def evaluate(self, instruction: str, success_criteria: str, code: str) -> Judgement:
        """
        Score a code submission against the ticket's success criteria.

        Args:
            instruction: The ticket instruction.
            success_criteria: What success looks like.
            code: The generated code to evaluate.

        Returns:
            Judgement with correctness, efficiency, hallucinations, and comments.
        """
        prompt = JUDGE_PROMPT_TEMPLATE.format(
            instruction=instruction,
            success_criteria=success_criteria,
            code=code,
        )

        try:
            response = await self._client.post(
                f"https://generativelanguage.googleapis.com/v1beta/models/{self.model}:generateContent",
                params={"key": self.api_key},
                json={
                    "contents": [{"parts": [{"text": prompt}]}],
                    "generationConfig": {"temperature": 0.1, "maxOutputTokens": 1024},
                },
            )
            response.raise_for_status()
            data = response.json()

            # Extract text from Gemini response
            text = data["candidates"][0]["content"]["parts"][0]["text"]
            parsed = _parse_judgement_json(text)

            return Judgement(
                correctness=max(1, min(5, int(parsed.get("correctness", 3)))),
                efficiency=max(1, min(5, int(parsed.get("efficiency", 3)))),
                hallucinations=parsed.get("hallucinations", []),
                comments=parsed.get("comments", ""),
            )

        except (httpx.HTTPError, KeyError, json.JSONDecodeError, IndexError) as e:
            logger.error("Judge LLM evaluation failed: %s", e)
            # Return a neutral judgement on failure so the experiment can continue
            return Judgement(
                correctness=3,
                efficiency=3,
                hallucinations=[],
                comments=f"Judge evaluation failed: {e}",
            )

    async def close(self) -> None:
        await self._client.aclose()
