"""Tests offline de comfyui_build_particle_texture_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_particle_texture_workflow import (  # noqa: E402
    comfyui_build_particle_texture_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 _id_of(wf, cls):
    return next(nid for nid, n in wf.items() if n["class_type"] == cls)


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


def _neg(wf):
    return next(
        n["inputs"]["text"]
        for n in wf.values()
        if n["class_type"] == "CLIPTextEncode" and "3d render" in n["inputs"]["text"]
    )


def test_golden_particle_on_black_recipe():
    wf = comfyui_build_particle_texture_workflow("spark", soft=False, seed=7)
    cls = _classes(wf)
    # Cadena base txt2img pura: NO lleva Rembg (el matting es luma-to-alpha aparte,
    # para preservar el falloff translucido que el additive blend del motor usa).
    assert "CheckpointLoaderSimple" in cls
    assert "KSampler" in cls
    assert "VAEDecode" in cls
    assert "SaveImage" in cls
    assert "Image Rembg (Remove Background)" not in cls
    # La particula + el aislamiento + el fondo negro aparecen en el prompt positivo.
    pos = _pos_with(wf, "spark")
    txt = pos["inputs"]["text"]
    assert "isolated on pure black background" in txt
    assert "single element" in txt
    assert "game particle system" in txt
    # SaveImage toma del VAEDecode directamente (sin recorte intermedio).
    vd_id = _id_of(wf, "VAEDecode")
    save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
    assert save["inputs"]["images"] == [vd_id, 0]


def test_edge_soft_toggles_edge_character():
    # soft=True -> bordes suaves / glow difuso (humo, destello, gota).
    wf_soft = comfyui_build_particle_texture_workflow("smoke puff", soft=True)
    pos_soft = _pos_with(wf_soft, "smoke puff")["inputs"]["text"]
    assert "soft edges" in pos_soft
    assert "soft glow" in pos_soft
    assert "feathered" in pos_soft
    assert "crisp sharp edges" not in pos_soft
    # soft=False -> bordes nitidos / alto contraste (chispa, copo, hoja).
    # style neutro para aislar el segmento de borde (el style default trae "soft glow").
    wf_hard = comfyui_build_particle_texture_workflow(
        "snowflake", soft=False, style="particle texture"
    )
    pos_hard = _pos_with(wf_hard, "snowflake")["inputs"]["text"]
    assert "crisp sharp edges" in pos_hard
    assert "high contrast" in pos_hard
    assert "feathered" not in pos_hard  # el segmento de borde suave no aparece
    # En ambos casos el fondo es negro plano (insumo de luma->alpha).
    assert "pure black background" in pos_soft
    assert "pure black background" in pos_hard


def test_edge_particle_reflected():
    for p in ["dust mote", "glowing flare", "water droplet", "energy ring", "falling leaf"]:
        wf = comfyui_build_particle_texture_workflow(p)
        pos = _pos_with(wf, p)
        assert p in pos["inputs"]["text"]


def test_edge_style_in_prompt():
    wf = comfyui_build_particle_texture_workflow(
        "glowing flare", style="anime sparkle, vibrant"
    )
    pos = _pos_with(wf, "glowing flare")
    assert "anime sparkle, vibrant" in pos["inputs"]["text"]


def test_edge_size_reflected_and_small_default():
    # Tamaño explicito se refleja en el latente cuadrado.
    wf = comfyui_build_particle_texture_workflow("spark", size=512)
    latent = _by_class(wf, "EmptyLatentImage")[0]["inputs"]
    assert latent["width"] == 512
    assert latent["height"] == 512  # cuadrado
    # Por defecto la textura de particula es pequeña (256): se replica a miles.
    wf_def = comfyui_build_particle_texture_workflow("spark")
    latent_def = _by_class(wf_def, "EmptyLatentImage")[0]["inputs"]
    assert latent_def["width"] == 256
    assert latent_def["height"] == 256


def test_edge_negative_isolates_single_particle():
    # El negativo por defecto rechaza escena/objeto solido/multiples particulas/marco
    # y fondo texturizado: queremos UN solo elemento limpio sobre negro plano.
    wf = comfyui_build_particle_texture_workflow("dust mote")
    neg = _neg(wf)
    assert "3d render" in neg
    assert "scene" in neg
    assert "multiple particles" in neg
    assert "vignette" in neg
    assert "frame" in neg
    assert "textured background" in neg  # fondo negro plano forzado


def test_edge_lora_reflected():
    wf = comfyui_build_particle_texture_workflow(
        "glowing flare", lora="SD15_vfx.safetensors", lora_strength=0.8
    )
    loras = _by_class(wf, "LoraLoader")
    assert len(loras) == 1
    assert loras[0]["inputs"]["lora_name"] == "SD15_vfx.safetensors"
    assert loras[0]["inputs"]["strength_model"] == 0.8


def test_error_empty_particle():
    try:
        comfyui_build_particle_texture_workflow("  ")
        assert False
    except ValueError as e:
        assert "particle" in str(e)


def test_determinism():
    a = comfyui_build_particle_texture_workflow(
        "spark", soft=False, lora="SD15_vfx.safetensors", seed=7
    )
    b = comfyui_build_particle_texture_workflow(
        "spark", soft=False, lora="SD15_vfx.safetensors", seed=7
    )
    assert a == b
