"""comfyui_judge_image — panel multi-juez: vota good/bad la calidad de una imagen.

Agregadora del grupo `comfyui-judge`. Es el "modelo adversario" que distingue producto
bueno de malo combinando tres jueces independientes:

  - estetico  (`comfyui_score_aesthetic`):       calidad visual LAION-V2, 0-10
  - fidelidad (`comfyui_score_clip_alignment`):  parecido al prompt via CLIP, 0-1
  - critico   (`comfyui_critique_image_llm`):    veredicto de un LLM-vision, good/bad + 0-10

Cada juez emite un voto good/bad; el veredicto final es por MAYORIA. El score agregado es
la media (ponderable) de los tres normalizados a 0-10. Si un juez falla, se excluye del
voto y se anota: el panel sigue votando con los restantes (no crashea). Solo devuelve
``ok=False`` si los tres jueces fallan.

Impura: compone tres funciones impuras (subprocesos torch + API Anthropic).
"""
import json
import os
import sys

sys.path.insert(0, os.path.dirname(__file__))

from comfyui_score_aesthetic import comfyui_score_aesthetic  # noqa: E402
from comfyui_score_clip_alignment import comfyui_score_clip_alignment  # noqa: E402
from comfyui_critique_image_llm import comfyui_critique_image_llm  # noqa: E402

DEFAULT_WEIGHTS = {"aesthetic": 1.0, "clip": 1.0, "llm": 1.0}


def comfyui_judge_image(
    image_path: str,
    prompt: str,
    *,
    weights: dict = None,
    threshold: float = 6.0,
    clip_threshold: float = 0.24,
    server: str = "127.0.0.1:8188",
    model: str = "claude-opus-4-8",
    venv_python: str = "~/ComfyUI/.venv/bin/python3",
) -> dict:
    """Juzga una imagen con el panel de tres jueces y devuelve el veredicto por mayoria.

    Args:
        image_path: ruta a la imagen en disco local.
        prompt: prompt original de la generacion (usado por fidelidad y critico).
        weights: pesos {aesthetic, clip, llm} para el score agregado; None = iguales.
            No afecta al voto por mayoria (1 juez = 1 voto). keyword-only.
        threshold: umbral 0-10 para el voto del juez estetico (score>=threshold => good).
            keyword-only.
        clip_threshold: umbral 0-1 para el voto de fidelidad (score>=clip_threshold => good).
            keyword-only.
        server: host:port del server ComfyUI (pasado a fidelidad). keyword-only.
        model: modelo Anthropic para el juez critico. keyword-only.
        venv_python: python del venv ComfyUI para los jueces estetico/fidelidad. keyword-only.

    Returns:
        dict ``{ok, verdict, score, votes, reasons, error, details}``.
        ``verdict`` 'good'|'bad' por mayoria de votos; ``score`` media ponderada 0-10 de los
        jueces que respondieron; ``votes`` = {clip, aesthetic, llm} cada uno 'good'|'bad'|
        'failed'; ``reasons`` agrega las razones del critico + notas de jueces caidos;
        ``details`` lleva el dict crudo de cada juez. ``ok=False`` solo si los tres fallan.
    """
    w = dict(DEFAULT_WEIGHTS)
    if weights:
        w.update({k: float(v) for k, v in weights.items() if k in w})

    aes = comfyui_score_aesthetic(image_path, venv_python=venv_python)
    clip = comfyui_score_clip_alignment(image_path, prompt, server=server,
                                        venv_python=venv_python)
    llm = comfyui_critique_image_llm(image_path, prompt, model=model)

    votes = {}
    reasons = []
    weighted = []  # (peso, score_0_10)

    # Juez estetico
    if aes.get("ok"):
        good = aes["score_0_10"] >= threshold
        votes["aesthetic"] = "good" if good else "bad"
        weighted.append((w["aesthetic"], aes["score_0_10"]))
        reasons.append(f"estetico={aes['score_0_10']:.2f}/10 ({votes['aesthetic']})")
    else:
        votes["aesthetic"] = "failed"
        reasons.append(f"estetico FALLO: {aes.get('error', '')}")

    # Juez de fidelidad
    if clip.get("ok"):
        good = clip["score_0_1"] >= clip_threshold
        votes["clip"] = "good" if good else "bad"
        weighted.append((w["clip"], clip["score_0_1"] * 10.0))
        reasons.append(f"fidelidad={clip['score_0_1']:.3f} ({votes['clip']})")
    else:
        votes["clip"] = "failed"
        reasons.append(f"fidelidad FALLO: {clip.get('error', '')}")

    # Juez critico LLM
    if llm.get("ok"):
        votes["llm"] = llm["verdict"]
        weighted.append((w["llm"], llm["score_0_10"]))
        reasons.append(f"critico={llm['score_0_10']:.1f}/10 ({llm['verdict']})")
        reasons.extend(f"  - {r}" for r in llm.get("reasons", []))
    else:
        votes["llm"] = "failed"
        reasons.append(f"critico FALLO: {llm.get('error', '')}")

    n_good = sum(1 for v in votes.values() if v == "good")
    n_bad = sum(1 for v in votes.values() if v == "bad")
    n_alive = n_good + n_bad

    if n_alive == 0:
        return {"ok": False, "verdict": "", "score": 0.0, "votes": votes,
                "reasons": reasons, "error": "los tres jueces fallaron",
                "details": {"aesthetic": aes, "clip": clip, "llm": llm}}

    # Mayoria; empate => 'bad' (conservador: ante la duda, no es producto).
    verdict = "good" if n_good > n_bad else "bad"

    tot_w = sum(pw for pw, _ in weighted)
    score = sum(pw * s for pw, s in weighted) / tot_w if tot_w else 0.0

    return {"ok": True, "verdict": verdict, "score": score, "votes": votes,
            "reasons": reasons, "error": "",
            "details": {"aesthetic": aes, "clip": clip, "llm": llm}}


def _main(argv):
    if len(argv) < 1:
        sys.stderr.write('uso: comfyui_judge_image IMAGEN ["prompt original"]\n')
        return 2
    image_path = argv[0]
    prompt = " ".join(argv[1:])
    res = comfyui_judge_image(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:]))
