"""comfyui_critique_image_llm — critica de un LLM-vision sobre una imagen generada.

Tercer juez del panel `comfyui-judge`: el criterio "humano" que detecta lo que los
scores numericos no ven — artefactos, anatomia rota, manos/dedos mal, texto ilegible,
composicion incoherente, watermarks. Compone `ask_llm_vision` (grupo claude-direct,
API directa de Anthropic) con un system prompt que obliga al modelo a devolver un
veredicto estructurado JSON: good/bad + score 0-10 + lista de razones.

Impura: red (API Anthropic) + lectura de la imagen. Respeta la regla `llm_invocation`:
SIEMPRE via claude-direct (`ask_llm_vision`), NUNCA `claude -p`. Cada llamada cuesta
tokens de la API; usar con mesura.
"""
import json
import os
import re
import sys

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "core"))

from ask_llm_vision import ask_llm_vision  # noqa: E402

DEFAULT_MODEL = "claude-opus-4-8"

_SYSTEM = (
    "Eres un juez experto de calidad de imagenes generadas por IA. Evaluas con dureza "
    "y objetividad: artefactos visuales, anatomia (manos, dedos, ojos, simetria), "
    "coherencia de la composicion, fidelidad al prompt, texto ilegible, watermarks. "
    "Respondes SIEMPRE y SOLO con un objeto JSON valido, sin texto adicional ni "
    "markdown, con esta forma exacta: "
    '{"verdict": "good"|"bad", "score": <numero 0-10>, "reasons": ["razon corta", ...]}. '
    "verdict='good' si la imagen es un producto usable y sin defectos graves; 'bad' si "
    "tiene artefactos, anatomia rota o no cumple el prompt. score: 0=basura, 10=perfecta. "
    "reasons: 1-4 frases cortas que justifican el veredicto."
)


def _extract_json(text: str) -> dict:
    """Extrae el primer objeto JSON del texto del modelo (tolera fences ```json)."""
    fenced = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL)
    candidate = fenced.group(1) if fenced else None
    if candidate is None:
        brace = re.search(r"\{.*\}", text, re.DOTALL)
        candidate = brace.group(0) if brace else None
    if candidate is None:
        raise ValueError("no se encontro objeto JSON en la respuesta")
    return json.loads(candidate)


def comfyui_critique_image_llm(
    image_path: str,
    prompt: str,
    *,
    model: str = DEFAULT_MODEL,
    max_tokens: int = 1024,
    token: str = "",
) -> dict:
    """Pide a un LLM-vision que critique una imagen y devuelve un veredicto estructurado.

    Args:
        image_path: ruta a la imagen en disco local.
        prompt: el prompt original con el que se genero la imagen (contexto para juzgar
            la fidelidad). Puede ir vacio si solo se evalua calidad intrinseca.
        model: id del modelo Anthropic con vision. Default "claude-opus-4-8". keyword-only.
        max_tokens: maximo de tokens de la respuesta. keyword-only.
        token: token OAuth; si vacio lo carga ask_llm_vision automaticamente. keyword-only.

    Returns:
        dict ``{ok, verdict, score_0_10, reasons, error}``. En exito ``ok=True``,
        ``verdict`` es 'good'|'bad', ``score_0_10`` el score del modelo y ``reasons`` la
        lista de razones. En error (imagen invalida, API caida, 429, JSON no parseable)
        ``ok=False`` con ``error`` describiendo la causa. Nunca lanza excepcion.
    """
    user_prompt = (
        f"Prompt original de la generacion: {prompt!r}.\n"
        if prompt and prompt.strip()
        else "No se aporto el prompt original; evalua la calidad intrinseca.\n"
    ) + "Evalua esta imagen y responde solo con el JSON pedido."

    res = ask_llm_vision(
        user_prompt, image_path,
        model=model, system=_SYSTEM, max_tokens=max_tokens, token=token,
    )
    if not res.get("ok"):
        return {"ok": False, "verdict": "", "score_0_10": 0.0, "reasons": [],
                "error": res.get("error", "ask_llm_vision fallo")}

    try:
        data = _extract_json(res["text"])
    except (ValueError, json.JSONDecodeError) as exc:
        return {"ok": False, "verdict": "", "score_0_10": 0.0, "reasons": [],
                "error": f"respuesta no parseable: {exc}; texto={res['text'][:200]!r}"}

    verdict = str(data.get("verdict", "")).strip().lower()
    if verdict not in ("good", "bad"):
        verdict = "bad"  # conservador ante respuesta ambigua
    try:
        score = float(data.get("score", 0.0))
    except (TypeError, ValueError):
        score = 0.0
    reasons = data.get("reasons", [])
    if not isinstance(reasons, list):
        reasons = [str(reasons)]
    reasons = [str(r) for r in reasons]

    return {"ok": True, "verdict": verdict, "score_0_10": score,
            "reasons": reasons, "error": ""}


def _main(argv):
    if len(argv) < 1:
        sys.stderr.write('uso: comfyui_critique_image_llm IMAGEN ["prompt original"]\n')
        return 2
    image_path = argv[0]
    prompt = " ".join(argv[1:])
    res = comfyui_critique_image_llm(image_path, prompt)
    print(json.dumps(res, indent=2, ensure_ascii=False))
    return 0 if res["ok"] else 1


if __name__ == "__main__":
    sys.exit(_main(sys.argv[1:]))
