"""Tests offline (sin red, sin GPU) de comfyui_build_title_lettering_workflow.

Verifican que el dict en API format se construye correctamente: clases presentes,
cableado del Rembg, prompt scaffold de logo de texto (text entrecomillado +
letter_style), formato apaisado por defecto, y reflejo de los argumentos
(text, letter_style, width/height, transparent, lora). No tocan el servidor ComfyUI.
"""
import os
import sys

import pytest

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

from ml.comfyui_build_title_lettering_workflow import (  # noqa: E402
    comfyui_build_title_lettering_workflow,
)


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


def _positive_prompt(wf):
    """Texto positivo: el CLIPTextEncode al que apunta KSampler.positive."""
    ks = next(n for n in wf.values() if n["class_type"] == "KSampler")
    pos_id = ks["inputs"]["positive"][0]
    return wf[pos_id]["inputs"]["text"]


def _negative_prompt(wf):
    ks = next(n for n in wf.values() if n["class_type"] == "KSampler")
    neg_id = ks["inputs"]["negative"][0]
    return wf[neg_id]["inputs"]["text"]


def test_golden_transparent():
    """Caso feliz: logo transparente -> Rembg cableado, prompt de lettering, clases base."""
    wf = comfyui_build_title_lettering_workflow(
        "DRAGON", letter_style="fire engraved", transparent=True, seed=42
    )
    cls = _classes(wf)
    for expected in {
        "CheckpointLoaderSimple",
        "KSampler",
        "VAEDecode",
        "SaveImage",
        "Image Rembg (Remove Background)",
    }:
        assert expected in cls, f"falta clase {expected}"

    prompt = _positive_prompt(wf)
    # El texto va ENTRECOMILLADO para que el modelo lo trate como cadena literal.
    assert '"DRAGON"' in prompt
    assert "game logo" in prompt
    assert "fire engraved" in prompt
    assert "lettering" in prompt
    assert "stylized typography" in prompt

    # SaveImage debe tomar la imagen del Rembg, no del VAEDecode.
    save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
    rembg_id = next(
        nid for nid, n in wf.items() if n["class_type"] == "Image Rembg (Remove Background)"
    )
    assert save["inputs"]["images"][0] == rembg_id
    rembg = wf[rembg_id]
    assert rembg["inputs"]["transparency"] is True


def test_edge_transparent_false_no_rembg():
    """transparent=False -> sin nodo Rembg; SaveImage cuelga del VAEDecode."""
    wf = comfyui_build_title_lettering_workflow("VOID", transparent=False)
    assert "Image Rembg (Remove Background)" not in _classes(wf)
    save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
    vae_id = next(nid for nid, n in wf.items() if n["class_type"] == "VAEDecode")
    assert save["inputs"]["images"][0] == vae_id


def test_edge_dims_reflected_landscape_default():
    """width/height se reflejan; default es apaisado 1024x512 (logo en una linea)."""
    wf = comfyui_build_title_lettering_workflow("QUEST", width=768, height=384)
    latent = next(n for n in wf.values() if n["class_type"] == "EmptyLatentImage")
    assert latent["inputs"]["width"] == 768
    assert latent["inputs"]["height"] == 384

    wf_default = comfyui_build_title_lettering_workflow("QUEST")
    latent_d = next(n for n in wf_default.values() if n["class_type"] == "EmptyLatentImage")
    assert latent_d["inputs"]["width"] == 1024
    assert latent_d["inputs"]["height"] == 512
    # Apaisado por defecto: el logo es mas ancho que alto.
    assert latent_d["inputs"]["width"] > latent_d["inputs"]["height"]


def test_edge_letter_style_reflected():
    """letter_style se inserta en el prompt positivo."""
    wf = comfyui_build_title_lettering_workflow("NEON", letter_style="neon sci-fi glow")
    assert "neon sci-fi glow" in _positive_prompt(wf)


def test_edge_text_reflected_quoted():
    """text se inserta literal y entrecomillado en el prompt positivo."""
    wf = comfyui_build_title_lettering_workflow("Mystic Realms")
    assert '"Mystic Realms"' in _positive_prompt(wf)


def test_edge_negative_allows_text():
    """El negativo por defecto NO rechaza texto como sujeto (aqui el texto ES el sujeto).

    Los hermanos (ui_hud, status_effect_icon) llevan tokens 'text'/'letters'/'words'
    en su negativo para limpiar la silueta; este builder NO, porque el lettering es el
    sujeto. Tokeniza el negativo y comprueba que esos tokens sueltos no estan (si que
    se permiten frases como 'extra letters'/'jumbled text' que rechazan el ruido).
    """
    neg = _negative_prompt(comfyui_build_title_lettering_workflow("LOGO"))
    tokens = {t.strip() for t in neg.split(",")}
    assert "text" not in tokens
    assert "letters" not in tokens
    assert "words" not in tokens


def test_edge_lora_injected():
    """lora -> LoraLoader presente con la fuerza dada."""
    wf = comfyui_build_title_lettering_workflow(
        "EMPIRE", lora="SD15_logo_style.safetensors", lora_strength=0.7
    )
    loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
    assert len(loras) == 1
    assert loras[0]["inputs"]["lora_name"] == "SD15_logo_style.safetensors"
    assert loras[0]["inputs"]["strength_model"] == pytest.approx(0.7)


def test_error_empty_text():
    """text vacio -> ValueError."""
    with pytest.raises(ValueError):
        comfyui_build_title_lettering_workflow("   ")


def test_determinism():
    """Mismos argumentos -> mismo dict (funcion pura)."""
    a = comfyui_build_title_lettering_workflow("ARENA", seed=7)
    b = comfyui_build_title_lettering_workflow("ARENA", seed=7)
    assert a == b
