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

Cubre el contrato del pipeline sin tocar la red:
- Golden: dispatch puro inyecta checkpoint/style/seed compartidos en cada workflow.
- Edge: el `kind` Godot por categoría (sprite/vfx/tileset) y seed = base_seed + i.
- Edge: emote exige el campo `expression`.
- Error: kind desconocido -> ok=False con la lista de kinds soportados, sin red.
- Flujo completo con submit/wait/fetch mockeados -> rutas + coherencia.
"""

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_asset_pack_oneshot as packmod  # noqa: E402
from pipelines.comfyui_generate_asset_pack_oneshot import (  # noqa: E402
    _build_item_workflow,
    comfyui_generate_asset_pack_oneshot,
    supported_kinds,
)


# --- Golden / Edge: dispatch puro (sin red) ---

def test_dispatch_shares_checkpoint_and_seed():
    wf, subject_eff, godot_kind, seed = _build_item_workflow(
        {"kind": "item_icon", "subject": "magic sword"},
        checkpoint="IMG_dreamshaper_8.safetensors", style="dark fantasy, hand-painted",
        lora=None, lora_strength=1.0, base_seed=42, size=512, index=0,
    )
    blob = json.dumps(wf)
    assert godot_kind == "sprite"
    assert seed == 42  # base_seed(42) + index(0)
    assert "IMG_dreamshaper_8.safetensors" in blob          # checkpoint compartido
    assert "dark fantasy, hand-painted" in subject_eff   # style inyectado al subject
    assert "magic sword" in subject_eff
    assert "42" in blob                                  # seed en el sampler


def test_seed_is_base_plus_index():
    _, _, _, seed = _build_item_workflow(
        {"kind": "enemy_creature", "subject": "goblin"},
        checkpoint="IMG_dreamshaper_8.safetensors", style="", lora=None,
        lora_strength=1.0, base_seed=100, size=None, index=3,
    )
    assert seed == 103


def test_godot_kind_per_category():
    # >=4 tipos distintos cubiertos por el dispatch, con su bucket Godot correcto.
    cases = {
        "item_icon": "sprite",
        "seamless_tile": "tileset",
        "particle_texture": "vfx",
        "weather_overlay": "vfx",
        "ui_hud": "sprite",
    }
    for kind, expected in cases.items():
        _, _, godot_kind, _ = _build_item_workflow(
            {"kind": kind, "subject": "x"},
            checkpoint="IMG_dreamshaper_8.safetensors", style="", lora=None,
            lora_strength=1.0, base_seed=0, size=None, index=0,
        )
        assert godot_kind == expected, kind


def test_emote_requires_expression():
    with pytest.raises(ValueError, match="expression"):
        _build_item_workflow(
            {"kind": "emote", "subject": "hero"},  # falta 'expression'
            checkpoint="IMG_dreamshaper_8.safetensors", style="", lora=None,
            lora_strength=1.0, base_seed=0, size=None, index=0,
        )
    # con expression sí construye
    wf, _, godot_kind, _ = _build_item_workflow(
        {"kind": "emote", "subject": "hero", "expression": "angry"},
        checkpoint="IMG_dreamshaper_8.safetensors", style="", lora=None,
        lora_strength=1.0, base_seed=0, size=None, index=0,
    )
    assert godot_kind == "sprite" and isinstance(wf, dict)


def test_per_item_kwargs_passthrough():
    # 'tier' es un kwarg propio de achievement_badge -> debe llegar al builder.
    wf, _, _, _ = _build_item_workflow(
        {"kind": "achievement_badge", "subject": "first kill", "tier": "platinum"},
        checkpoint="IMG_dreamshaper_8.safetensors", style="", lora=None,
        lora_strength=1.0, base_seed=0, size=None, index=0,
    )
    assert "platinum" in json.dumps(wf)


# --- Error: kind desconocido no toca la red ---

def test_unknown_kind_fails_without_network():
    res = comfyui_generate_asset_pack_oneshot(
        [{"kind": "item_icon", "subject": "ok"},
         {"kind": "does_not_exist", "subject": "x"}],
        checkpoint="IMG_dreamshaper_8.safetensors",
    )
    assert res["ok"] is False
    assert "no soportado" in res["error"]
    # el error enumera kinds reales soportados
    assert any(k in res["error"] for k in supported_kinds())
    assert res["generated"] == 0


def test_empty_pack():
    res = comfyui_generate_asset_pack_oneshot([])
    assert res["ok"] is False and "vacío" in res["error"]


# --- Flujo completo con transporte mockeado ---

def test_full_flow_mocked(monkeypatch, tmp_path):
    calls = {"submit": 0}

    def fake_submit(workflow, server="127.0.0.1:8188", **kw):
        calls["submit"] += 1
        # el server debe llegar normalizado (sin esquema)
        assert "://" not in server
        return {"prompt_id": f"pid-{calls['submit']}", "client_id": "c"}

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

    def fake_fetch(filename, *, subfolder="", type_="output",
                   server="127.0.0.1:8188", dest_dir=".", timeout=60.0):
        path = os.path.join(dest_dir, filename)
        with open(path, "wb") as fh:
            fh.write(b"\x89PNG\r\n")
        return {"ok": True, "path": path}

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

    res = comfyui_generate_asset_pack_oneshot(
        [{"kind": "item_icon", "subject": "magic sword"},
         {"kind": "enemy_creature", "subject": "goblin warrior"}],
        checkpoint="IMG_dreamshaper_8.safetensors", style="dark fantasy, hand-painted",
        base_seed=42, size=512, server="http://127.0.0.1:8188",
        out_dir=str(tmp_path),
    )
    assert res["ok"] is True, res["error"]
    assert res["generated"] == 2 and res["failed"] == 0 and res["count"] == 2
    assert [a["seed"] for a in res["assets"]] == [42, 43]
    assert all(a["prompt_id"].startswith("pid-") for a in res["assets"])
    assert all(os.path.isfile(a["path"]) for a in res["assets"])
    assert "IMG_dreamshaper_8.safetensors" in res["coherence_note"]
    assert res["checkpoint"] == "IMG_dreamshaper_8.safetensors"


def test_one_asset_fails_others_survive(monkeypatch, tmp_path):
    def fake_submit(workflow, server="127.0.0.1:8188", **kw):
        # falla el segundo submit (simula OOM/rechazo), el primero pasa
        fake_submit.n += 1
        if fake_submit.n == 2:
            raise RuntimeError("simulated OOM")
        return {"prompt_id": "pid-1", "client_id": "c"}
    fake_submit.n = 0

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

    def fake_fetch(filename, *, subfolder="", type_="output",
                   server="127.0.0.1:8188", dest_dir=".", timeout=60.0):
        path = os.path.join(dest_dir, filename)
        open(path, "wb").write(b"x")
        return {"ok": True, "path": path}

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

    res = comfyui_generate_asset_pack_oneshot(
        [{"kind": "item_icon", "subject": "sword"},
         {"kind": "prop_object", "subject": "barrel"}],
        out_dir=str(tmp_path),
    )
    assert res["ok"] is False           # un fallo aislado -> pack no perfecto
    assert res["generated"] == 1 and res["failed"] == 1
    assert res["assets"][0]["ok"] is True
    assert res["assets"][1]["ok"] is False and "OOM" in res["assets"][1]["error"]
