"""Tests de comfyui_generate_styled_asset_oneshot (offline; sin ComfyUI ni GPU).

Cubre el contrato del pipeline sin tocar la red ni la GPU:
- Error: kind desconocido / estilo desconocido / subject vacío -> ok=False SIN red.
- Golden build: el preset elige checkpoint/lora/negativo y el workflow los lleva; el
  `style` del preset SUSTITUYE el del builder; transparent/size del preset se aplican.
- Golden auto-post: un estilo con post (gameboy) auto-pixeliza el PNG -> `path` final
  != `raw_path` y sale ya pixelizado, sin paso manual.
- Edge: un estilo SIN post (cyberpunk-neon) deja `path` == `raw_path` (no toca el crudo).
- Edge: catálogo ampliado a >=5 estilos; cada estilo nuevo construye un workflow válido.
- Error: un fallo de post deja el crudo en raw_path y ok=False con error claro.
- Edge: export a Godot usa el asset FINAL y lo manda como `pixelart` si hubo pixelize.
"""

import json
import os
import sys

import pytest

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import pipelines.comfyui_generate_styled_asset_oneshot as smod  # noqa: E402
from pipelines.comfyui_generate_styled_asset_oneshot import (  # noqa: E402
    comfyui_generate_styled_asset_oneshot,
    styled_asset_build_only,
)
from ml.comfyui_get_gamedev_style_preset import comfyui_get_gamedev_style_preset  # noqa: E402


# --- Error paths que NO tocan la red ---

def test_unknown_kind_fails_without_network():
    res = comfyui_generate_styled_asset_oneshot("does_not_exist", "knight", "gameboy")
    assert res["ok"] is False
    assert "no soportado" in res["error"]
    assert res["prompt_id"] == ""  # no llegó a encolar


def test_unknown_style_fails_without_network():
    res = comfyui_generate_styled_asset_oneshot("item_icon", "potion", "does_not_exist")
    assert res["ok"] is False
    assert "desconocido" in res["error"]
    assert res["prompt_id"] == ""


def test_empty_subject_fails_without_network():
    res = comfyui_generate_styled_asset_oneshot("item_icon", "   ", "gameboy")
    assert res["ok"] is False
    assert res["prompt_id"] == ""


# --- Golden build puro: el preset dirige el workflow ---

def test_build_only_preset_drives_workflow():
    built = styled_asset_build_only("item_icon", "knight character", "gameboy", seed=7)
    blob = json.dumps(built["workflow"])
    # checkpoint del preset gameboy (SD1.5)
    assert "IMG_dreamshaper_8.safetensors" in blob
    # el subject combinó el sufijo del estilo gameboy
    assert "8-bit" in built["applied"]["subject"]
    # el style del preset SUSTITUYE el style categórico del builder
    assert "Game Boy" in blob
    # gameboy declara post pixelize (lo que el pipeline auto-aplicará)
    assert built["applied"]["post"].get("pixelize")
    assert built["godot_kind"] == "sprite"


def test_build_only_lora_preset_injects_lora():
    # cartoon-cel-shaded usa SD15_anime_style_box -> el workflow debe inyectar el LoRA
    built = styled_asset_build_only("enemy_creature", "goblin", "cartoon-cel-shaded", seed=1)
    blob = json.dumps(built["workflow"])
    assert "SD15_anime_style_box.safetensors" in blob
    assert any(n.get("class_type") == "LoraLoader" for n in built["workflow"].values())


# --- Catálogo ampliado: >=5 estilos, cada uno construible ---

def test_catalog_has_at_least_5_styles():
    cat = comfyui_get_gamedev_style_preset()
    assert cat["count"] >= 5
    for nuevo in ("cyberpunk-neon", "low-poly-flat", "cartoon-cel-shaded"):
        assert nuevo in cat["names"]


def test_every_style_builds_a_valid_workflow():
    cat = comfyui_get_gamedev_style_preset()
    for style in cat["names"]:
        built = styled_asset_build_only("item_icon", "magic ring", style, seed=3)
        wf = built["workflow"]
        classes = {n.get("class_type") for n in wf.values()}
        assert "KSampler" in classes and "SaveImage" in classes, style


# --- Mock de transporte: submit/wait/fetch sin red ---

def _mock_transport(monkeypatch, *, raw_name="gen.png"):
    def fake_submit(workflow, server="127.0.0.1:8188", **kw):
        assert "://" not in server  # server normalizado
        return {"prompt_id": "pid-1", "client_id": "c"}

    def fake_wait(prompt_id, server="127.0.0.1:8188", timeout=600.0, **kw):
        return {"9": {"images": [{"filename": raw_name, "subfolder": "", "type": "output"}]}}

    def fake_fetch(filename, *, subfolder="", type_="output",
                   server="127.0.0.1:8188", dest_dir=".", timeout=60.0):
        os.makedirs(dest_dir, exist_ok=True)
        path = os.path.join(dest_dir, filename)
        # PNG real mínimo (8x8 RGB) para que el pixelize de verdad pueda abrirlo.
        from PIL import Image
        Image.new("RGB", (64, 64), (123, 80, 200)).save(path)
        return {"ok": True, "path": path, "size_bytes": 100, "error": ""}

    monkeypatch.setattr(smod, "comfyui_submit_workflow", fake_submit)
    monkeypatch.setattr(smod, "comfyui_wait_result", fake_wait)
    monkeypatch.setattr(smod, "comfyui_fetch_output_image", fake_fetch)


# --- Golden auto-post: gameboy pixeliza automáticamente ---

def test_post_auto_applied_for_gameboy(monkeypatch, tmp_path):
    _mock_transport(monkeypatch)
    res = comfyui_generate_styled_asset_oneshot(
        "item_icon", "health potion", "gameboy", seed=7,
        server="http://127.0.0.1:8188", out_dir=str(tmp_path),
    )
    assert res["ok"] is True, res["error"]
    assert res["raw_path"] and os.path.isfile(res["raw_path"])
    # el path FINAL es el post-procesado, distinto del crudo
    assert res["path"] != res["raw_path"]
    assert os.path.isfile(res["path"])
    assert res["post_applied"]["kind"] == "pixelize"
    # paleta game-boy -> a lo sumo 4 colores en el resultado
    assert 0 < res["post_applied"]["n_colors_final"] <= 4


# --- Edge: estilo SIN post no toca el crudo ---

def test_no_post_keeps_raw_for_cyberpunk(monkeypatch, tmp_path):
    _mock_transport(monkeypatch)
    res = comfyui_generate_styled_asset_oneshot(
        "enemy_creature", "street samurai", "cyberpunk-neon", seed=5,
        out_dir=str(tmp_path),
    )
    assert res["ok"] is True, res["error"]
    assert res["path"] == res["raw_path"]  # sin post -> final == crudo
    assert res["post_applied"] is None
    assert res["post"] == {}


# --- Error: fallo de post deja el crudo y ok=False ---

def test_post_failure_keeps_raw_and_flags_error(monkeypatch, tmp_path):
    _mock_transport(monkeypatch)

    def fake_pixelize(src, dst, **kw):
        return {"ok": False, "out_path": "", "error": "simulated PIL failure"}

    monkeypatch.setattr(smod, "comfyui_pixelize_image", fake_pixelize)
    res = comfyui_generate_styled_asset_oneshot(
        "item_icon", "sword", "gameboy", seed=1, out_dir=str(tmp_path),
    )
    assert res["ok"] is False
    assert res["raw_path"] and os.path.isfile(res["raw_path"])  # crudo preservado
    assert "post pixelize falló" in res["error"]
    assert res["post_applied"]["ok"] is False


# --- Edge: export a Godot usa el asset final como pixelart si hubo post ---

def test_export_godot_uses_pixelart_when_post(monkeypatch, tmp_path):
    _mock_transport(monkeypatch)
    captured = {}

    def fake_export(asset_path, kind, godot_project, *, reimport=True,
                    name=None, godot_bin=None):
        captured["kind"] = kind
        captured["asset"] = asset_path
        return {"ok": True, "kind": kind}

    monkeypatch.setattr(smod, "comfyui_export_asset_to_godot", fake_export)
    res = comfyui_generate_styled_asset_oneshot(
        "item_icon", "potion", "gameboy", seed=2, out_dir=str(tmp_path),
        export_godot=str(tmp_path / "godot_proj"),
    )
    assert res["ok"] is True, res["error"]
    assert captured["kind"] == "pixelart"          # post -> Nearest filter en Godot
    assert captured["asset"] == res["path"]         # exporta el FINAL post-procesado
    assert res["exported"]["ok"] is True
