"""Tests offline para comfyui_bump_skill_version (impura, sin red/GPU).

Verifican el contrato del bucle de mejora contra una librería temporal:
- golden: score sube → promueve, snapshot pre-mutación, semver subido, recipe_patch aplicado,
  growth_log con una línea bien formada,
- edge: bump major/patch + deep-merge anidado sin pisar otras claves,
- error: gate (score no mejora sin force) → ok=False; force salta el gate; skill inexistente.
"""

import json
import os
import sys

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

import pytest  # noqa: E402

from ml.comfyui_bump_skill_version import comfyui_bump_skill_version, _bump_semver, _deep_merge  # noqa: E402


def _seed_skill(lib, slug="demo_skill", version="1.0.0", **extra):
    sdir = os.path.join(lib, slug)
    os.makedirs(os.path.join(sdir, "versions"), exist_ok=True)
    recipe = {"schema_version": 1, "slug": slug, "version": version,
              "base_workflow": "txt2img", "checkpoint": "IMG_dreamshaper_8.safetensors",
              "params": {"steps": 28, "cfg": 6.0}, **extra}
    with open(os.path.join(sdir, "recipe.json"), "w", encoding="utf-8") as f:
        json.dump(recipe, f)
    return sdir


def test_semver_helper():
    assert _bump_semver("1.0.0", "minor") == "1.1.0"
    assert _bump_semver("1.2.3", "major") == "2.0.0"
    assert _bump_semver("1.2.3", "patch") == "1.2.4"
    assert _bump_semver("nan", "minor") == "0.1.0"  # versión mal formada → 0.0.0 base


def test_deep_merge_no_pisa_otras_claves():
    base = {"params": {"steps": 28, "cfg": 6.0}, "checkpoint": "a"}
    out = _deep_merge(base, {"params": {"steps": 32}})
    assert out == {"params": {"steps": 32, "cfg": 6.0}, "checkpoint": "a"}
    assert base["params"]["steps"] == 28  # no muta el original


def test_golden_promueve_cuando_score_sube(tmp_path):
    lib = str(tmp_path)
    _seed_skill(lib)
    res = comfyui_bump_skill_version(
        "demo_skill", "subir steps a 32", score_before=6.5, score_after=7.4,
        judge_run_id="judge_xyz", recipe_patch={"params": {"steps": 32}}, library_dir=lib)

    assert res["ok"] is True
    assert res["old_version"] == "1.0.0"
    assert res["new_version"] == "1.1.0"

    # snapshot pre-mutación conserva la receta vieja (steps 28).
    with open(res["snapshot_file"], encoding="utf-8") as f:
        snap = json.load(f)
    assert snap["params"]["steps"] == 28
    assert snap["version"] == "1.0.0"

    # recipe.json mutado: patch aplicado + semver subido, cfg intacto.
    with open(os.path.join(lib, "demo_skill", "recipe.json"), encoding="utf-8") as f:
        cur = json.load(f)
    assert cur["params"]["steps"] == 32
    assert cur["params"]["cfg"] == 6.0
    assert cur["version"] == "1.1.0"

    # growth_log: una línea con la evidencia.
    with open(os.path.join(lib, "demo_skill", "growth_log.jsonl"), encoding="utf-8") as f:
        lines = [json.loads(x) for x in f if x.strip()]
    assert len(lines) == 1
    entry = lines[0]
    assert entry["version"] == "1.1.0"
    assert entry["score_before"] == 6.5
    assert entry["score_after"] == 7.4
    assert entry["judge_run_id"] == "judge_xyz"
    assert entry["diff"] == {"params": {"steps": 32}}


def test_edge_major_y_patch(tmp_path):
    lib = str(tmp_path)
    _seed_skill(lib, slug="s_major", version="1.4.2")
    r1 = comfyui_bump_skill_version("s_major", "rework", score_before=5.0, score_after=8.0,
                                    bump="major", library_dir=lib)
    assert r1["new_version"] == "2.0.0"

    _seed_skill(lib, slug="s_patch", version="1.4.2")
    r2 = comfyui_bump_skill_version("s_patch", "tweak", score_before=5.0, score_after=5.1,
                                    bump="patch", library_dir=lib)
    assert r2["new_version"] == "1.4.3"


def test_error_gate_bloquea_si_no_mejora(tmp_path):
    lib = str(tmp_path)
    sdir = _seed_skill(lib)
    res = comfyui_bump_skill_version("demo_skill", "no mejora", score_before=7.0,
                                     score_after=6.5, library_dir=lib)
    assert res["ok"] is False
    assert "gate" in res["error"]
    # No se tocó nada: ni snapshot ni growth_log ni cambio de versión.
    assert not os.path.exists(os.path.join(sdir, "growth_log.jsonl"))
    with open(os.path.join(sdir, "recipe.json"), encoding="utf-8") as f:
        assert json.load(f)["version"] == "1.0.0"


def test_error_force_salta_gate(tmp_path):
    lib = str(tmp_path)
    _seed_skill(lib)
    res = comfyui_bump_skill_version("demo_skill", "forzado", score_before=7.0,
                                     score_after=6.5, force=True, library_dir=lib)
    assert res["ok"] is True
    assert res["new_version"] == "1.1.0"
    assert res["growth_entry"]["forced"] is True


def test_error_skill_inexistente(tmp_path):
    res = comfyui_bump_skill_version("no_existe", "x", score_before=1.0, score_after=2.0,
                                     library_dir=str(tmp_path))
    assert res["ok"] is False
    assert "no encontrada" in res["error"]


if __name__ == "__main__":
    sys.exit(pytest.main([__file__, "-q"]))
