"""Tests offline de comfyui_build_portrait_avatar_workflow (estructura del dict, sin GPU)."""

import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from ml.comfyui_build_portrait_avatar_workflow import (  # noqa: E402
    comfyui_build_portrait_avatar_workflow,
)


def _classes(wf):
    return sorted({n["class_type"] for n in wf.values()})


def _by_class(wf, cls):
    return [n for n in wf.values() if n["class_type"] == cls]


def _positive_node(wf, needle):
    return next(
        n
        for n in wf.values()
        if n["class_type"] == "CLIPTextEncode" and needle in n["inputs"]["text"]
    )


def test_golden_recipe_facedetailer():
    wf = comfyui_build_portrait_avatar_workflow(
        "a young knight woman with red hair", seed=7
    )
    cls = _classes(wf)
    # Cadena base txt2img + FaceDetailer (default facedetailer=True).
    assert "CheckpointLoaderSimple" in cls
    assert "KSampler" in cls
    assert "VAEDecode" in cls
    assert "FaceDetailer" in cls
    assert "UltralyticsDetectorProvider" in cls
    # Sin ref_face -> no hay rama IPAdapter-FaceID.
    assert "IPAdapterFaceID" not in cls
    # El personaje aparece en el prompt con el scaffold de retrato.
    pos = _positive_node(wf, "a young knight woman with red hair")
    assert "portrait of" in pos["inputs"]["text"]
    assert "bust shot" in pos["inputs"]["text"]
    assert "looking at viewer" in pos["inputs"]["text"]
    # FaceDetailer activado -> el unico SaveImage es el del detailer (fd_save).
    saves = _by_class(wf, "SaveImage")
    assert len(saves) == 1
    fd_face_id = next(nid for nid, n in wf.items() if n["class_type"] == "FaceDetailer")
    assert saves[0]["inputs"]["images"] == [fd_face_id, 0]
    # El FaceDetailer toma la imagen del VAEDecode del base.
    vd_id = next(nid for nid, n in wf.items() if n["class_type"] == "VAEDecode")
    fd = _by_class(wf, "FaceDetailer")[0]
    assert fd["inputs"]["image"] == [vd_id, 0]


def test_edge_no_facedetailer_keeps_base_save():
    wf = comfyui_build_portrait_avatar_workflow("an old wizard", facedetailer=False)
    cls = _classes(wf)
    assert "FaceDetailer" not in cls
    # SaveImage base presente y toma del VAEDecode.
    saves = _by_class(wf, "SaveImage")
    assert len(saves) == 1
    vd_id = next(nid for nid, n in wf.items() if n["class_type"] == "VAEDecode")
    assert saves[0]["inputs"]["images"] == [vd_id, 0]


def test_edge_ref_face_activates_faceid():
    wf = comfyui_build_portrait_avatar_workflow(
        "a young knight woman with red hair",
        ref_face="hero_face.png",
        weight=0.9,
        facedetailer=False,
    )
    cls = _classes(wf)
    assert "IPAdapterUnifiedLoaderFaceID" in cls
    assert "IPAdapterFaceID" in cls
    # La imagen de referencia se carga con LoadImage.
    loads = [n for n in _by_class(wf, "LoadImage")]
    assert any(n["inputs"]["image"] == "hero_face.png" for n in loads)
    # weight reflejado en el nodo FaceID.
    faceid = _by_class(wf, "IPAdapterFaceID")[0]
    assert faceid["inputs"]["weight"] == 0.9
    # El KSampler consume el MODEL condicionado por la rama IPAdapter.
    ks = _by_class(wf, "KSampler")[0]
    apply_id = next(nid for nid, n in wf.items() if n["class_type"] == "IPAdapterFaceID")
    assert ks["inputs"]["model"] == [apply_id, 0]


def test_edge_style_reflected_in_prompt():
    wf = comfyui_build_portrait_avatar_workflow(
        "a cheerful bard", style="anime portrait, cel shaded", facedetailer=False
    )
    pos = _positive_node(wf, "a cheerful bard")
    assert "anime portrait, cel shaded" in pos["inputs"]["text"]


def test_edge_size_reflected_square():
    wf = comfyui_build_portrait_avatar_workflow(
        "a stoic paladin", size=768, facedetailer=False
    )
    latent = _by_class(wf, "EmptyLatentImage")[0]["inputs"]
    assert latent["width"] == 768
    assert latent["height"] == 768  # cuadrado (avatar)


def test_edge_lora_reflected():
    wf = comfyui_build_portrait_avatar_workflow(
        "a dark sorceress",
        lora="SD15_anime_lineart.safetensors",
        lora_strength=0.8,
        facedetailer=False,
    )
    loras = _by_class(wf, "LoraLoader")
    assert len(loras) == 1
    assert loras[0]["inputs"]["lora_name"] == "SD15_anime_lineart.safetensors"
    assert loras[0]["inputs"]["strength_model"] == 0.8


def test_edge_seed_reflected():
    wf = comfyui_build_portrait_avatar_workflow(
        "a grizzled mercenary", seed=123, facedetailer=False
    )
    ks = _by_class(wf, "KSampler")[0]
    assert ks["inputs"]["seed"] == 123


def test_error_empty_character():
    try:
        comfyui_build_portrait_avatar_workflow("   ")
        assert False
    except ValueError as e:
        assert "character" in str(e)


def test_determinism():
    a = comfyui_build_portrait_avatar_workflow(
        "a young knight woman with red hair", lora="SD15_anime_lineart.safetensors", seed=7
    )
    b = comfyui_build_portrait_avatar_workflow(
        "a young knight woman with red hair", lora="SD15_anime_lineart.safetensors", seed=7
    )
    assert a == b
