"""
core/scorer.py — Évaluation LLM-as-Judge asynchrone + envoi des scores à Langfuse.

Appelé après chaque réponse de l'agent (depuis api.py et main.py).
S'exécute dans un thread daemon pour ne pas bloquer la réponse.

Critères évalués (1–5) :
  pertinence — la réponse adresse-t-elle la question posée ?
  fidelite   — les faits sont-ils corrects et vérifiables ?
  coherence  — la réponse est-elle claire, structurée, sans contradiction ?
"""

from __future__ import annotations

import json
import logging
import os
import re
import threading

import base64
import httpx

import domain.core.langfuse_http as langfuse_http
from domain.core.llm import appeler_llm

logger = logging.getLogger("scorer")

JUGE_MODEL = "gpt-4o-mini"

_SYSTEM_JUGE = """Tu es un expert en évaluation d'agents IA spécialisés en immobilier.
Évalue la réponse d'un agent immobilier selon 3 critères notés de 1 à 5.

Critères :
- pertinence (1-5) : la réponse répond-elle directement à la question ?
  1 = hors sujet, 5 = répond précisément à tout ce qui était demandé
- fidelite (1-5) : les informations factuelles sont-elles correctes et cohérentes ?
  1 = erreurs graves, 5 = données fiables et vérifiables
- coherence (1-5) : la réponse est-elle claire, bien structurée, sans contradiction ?
  1 = confuse ou contradictoire, 5 = très claire et bien organisée

Réponds UNIQUEMENT avec un objet JSON valide, sans texte avant ou après :
{"pertinence": N, "fidelite": N, "coherence": N, "justification": "une phrase"}"""


def _parser_verdict(raw: str) -> dict | None:
    try:
        return json.loads(raw)
    except json.JSONDecodeError:
        pass
    match = re.search(r'\{[^{}]*"pertinence"[^{}]*\}', raw, re.DOTALL)
    if match:
        try:
            return json.loads(match.group())
        except json.JSONDecodeError:
            pass
    return None


def _evaluer(question: str, reponse: str) -> dict:
    """Appelle le LLM-juge et retourne le verdict normalisé."""
    prompt = f"Question posée : {question}\n\nRéponse de l'agent :\n{reponse}"

    for _ in range(2):
        raw = appeler_llm(prompt, system_prompt=_SYSTEM_JUGE, model=JUGE_MODEL)
        verdict = _parser_verdict(raw)
        if verdict and all(k in verdict for k in ("pertinence", "fidelite", "coherence")):
            for cle in ("pertinence", "fidelite", "coherence"):
                try:
                    verdict[cle] = max(1, min(5, int(verdict[cle])))
                except (TypeError, ValueError):
                    verdict[cle] = 3
            verdict.setdefault("justification", "—")
            return verdict

    return {"pertinence": 3, "fidelite": 3, "coherence": 3,
            "justification": "[erreur parsing juge]"}


def _poster_scores(trace_id: str, verdict: dict) -> None:
    """Envoie les 3 scores + score global sur la trace Langfuse."""
    score_global = (verdict["pertinence"] + verdict["fidelite"] + verdict["coherence"]) / 3
    justif = verdict.get("justification", "—")

    scores = [
        {"traceId": trace_id, "name": "pertinence", "value": float(verdict["pertinence"]),
         "dataType": "NUMERIC", "comment": justif},
        {"traceId": trace_id, "name": "fidelite",   "value": float(verdict["fidelite"]),
         "dataType": "NUMERIC", "comment": justif},
        {"traceId": trace_id, "name": "coherence",  "value": float(verdict["coherence"]),
         "dataType": "NUMERIC", "comment": justif},
        {"traceId": trace_id, "name": "score_global", "value": round(score_global, 2),
         "dataType": "NUMERIC", "comment": justif},
    ]

    host = os.getenv("LANGFUSE_HOST", "http://localhost:3000").rstrip("/")
    pk   = os.getenv("LANGFUSE_PUBLIC_KEY", "")
    sk   = os.getenv("LANGFUSE_SECRET_KEY", "")
    if not pk or not sk:
        return
    auth    = "Basic " + base64.b64encode(f"{pk}:{sk}".encode()).decode()
    headers = {"Authorization": auth, "Content-Type": "application/json"}

    for s in scores:
        try:
            r = httpx.post(f"{host}/api/public/scores", json=s, headers=headers, timeout=5)
            if r.status_code not in (200, 201):
                logger.debug("Score %s: %s %s", s["name"], r.status_code, r.text[:100])
        except Exception as exc:
            logger.debug("Erreur envoi score %s: %s", s["name"], exc)

    logger.info(
        "Scores Langfuse postés — pertinence=%s fidelite=%s coherence=%s global=%.2f",
        verdict["pertinence"], verdict["fidelite"], verdict["coherence"], score_global,
    )


def noter_async(trace_id: str, question: str, reponse: str) -> None:
    """Lance l'évaluation LLM-as-Judge dans un thread daemon."""
    def _run():
        try:
            verdict = _evaluer(question, reponse)
            _poster_scores(trace_id, verdict)
        except Exception as exc:
            logger.warning("Évaluation échouée (non-bloquant) : %s", exc)

    threading.Thread(target=_run, daemon=True, name="scorer").start()
