"""Unit tests for the pure engine parts (no network, no demucs).

    cd tools/foundry && python3 -m pytest tests/ -q
"""
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from engine import fetch as F
from engine import separate as S
from engine.model import Catch, slugify


# ── slug ────────────────────────────────────────────────────────────────────
def test_slugify_basic():
    assert slugify("Loituma \"Ievan polkka\"") == "loituma_ievan_polkka"
    assert slugify("  N'to - La Clé ") == "n_to_la_cl"
    assert slugify("///") == "untitled"


# ── info → Catch (the parse, tested with a fixture dict) ─────────────────────
INFO = {
    "id": "hqthspSKZV8",
    "title": 'Loituma "Ievan polkka"',
    "webpage_url": "https://www.youtube.com/watch?v=hqthspSKZV8",
    "duration": 215.0,
}


def test_catch_from_info():
    c = F.catch_from_info(INFO, source_path="source")
    assert c.title == 'Loituma "Ievan polkka"'
    assert c.source_id == "hqthspSKZV8"
    assert c.source_url.endswith("hqthspSKZV8")
    assert c.duration == 215.0
    assert c.slug.startswith("loituma_ievan_polkka")
    assert "hqthspskzv8" in c.slug  # id appended for uniqueness
    assert not c.separated


def test_catch_from_info_id_already_in_title():
    c = F.catch_from_info({"id": "abc", "title": "track abc"}, source_path="source")
    assert c.slug == "track_abc"  # id not doubled


# ── argv builders ─────────────────────────────────────────────────────────
def test_probe_and_download_argv():
    assert "--dump-single-json" in F.probe_argv("u")
    argv = F.download_argv("u", Path("/tmp/x"))
    assert "-f" in argv and "bestaudio/best" in argv
    assert any("source.%(ext)s" in a for a in argv)


# ── persistence round-trip ──────────────────────────────────────────────────
def test_catch_save_load(tmp_path):
    c = F.catch_from_info(INFO, source_path="source.webm")
    c.save(tmp_path)
    again = Catch.load(c.dir(tmp_path))
    assert again.slug == c.slug and again.title == c.title


# ── backend registry ─────────────────────────────────────────────────────
def test_registry_has_the_engines():
    assert set(S.REGISTRY) == {"demucs", "roformer", "merge"}


def test_roformer_produces_two_stems_and_builds_cmd():
    be = S.get_backend("roformer")
    assert be.produces == ("vocals", "instrumental")
    cmd = be.build_cmd(Path("/w/s/source.webm"), Path("/tmp/o"), model="m.ckpt")
    assert "--model_filename" in cmd and "m.ckpt" in cmd and "--output_dir" in cmd


def test_merge_is_composite_producing_four_stems():
    be = S.get_backend("merge")
    assert be.composite is True
    assert be.produces == ("drums", "bass", "other", "vocals")


def test_demucs_cmd_build():
    be = S.get_backend("demucs")
    cmd = be.build_cmd(Path("/w/s/source.webm"), Path("/w/s/_separated"),
                       model="htdemucs", shifts=4, jobs=2)
    assert "-n" in cmd and "htdemucs" in cmd
    assert "--shifts" in cmd and "4" in cmd
    assert "-j" in cmd and "2" in cmd
    assert cmd[-1].endswith("source.webm")


def test_demucs_stem_dir():
    be = S.get_backend("demucs")
    d = be.stem_dir(Path("/w/s/source.webm"), Path("/w/s/_separated"), "htdemucs")
    assert d == Path("/w/s/_separated/htdemucs/source")


def test_backend_available_returns_status_and_hint():
    # availability depends on which venvs exist on the box; the contract is (bool, str)
    for name in ("demucs", "roformer", "merge"):
        ok, hint = S.get_backend(name).available()
        assert isinstance(ok, bool) and isinstance(hint, str) and hint


def test_unknown_backend_raises():
    import pytest
    with pytest.raises(KeyError):
        S.get_backend("nope")


# ── publish: close-the-loop symlink into Dirt-Samples ───────────────────────
from engine import publish as P  # noqa: E402


def test_link_kit_creates_symlink(tmp_path):
    samples = tmp_path / "Samples"; dirt = tmp_path / "Dirt"
    (samples / "crimewave").mkdir(parents=True)
    r = P.link_kit("crimewave", samples=samples, dirt=dirt)
    assert r.action == "created"
    assert (dirt / "crimewave").is_symlink()
    assert (dirt / "crimewave").resolve() == (samples / "crimewave").resolve()


def test_link_kit_idempotent(tmp_path):
    samples = tmp_path / "Samples"; dirt = tmp_path / "Dirt"
    (samples / "k").mkdir(parents=True)
    P.link_kit("k", samples=samples, dirt=dirt)
    assert P.link_kit("k", samples=samples, dirt=dirt).action == "already-linked"


def test_link_kit_repoints_stale(tmp_path):
    samples = tmp_path / "Samples"; dirt = tmp_path / "Dirt"
    (samples / "k").mkdir(parents=True)
    (tmp_path / "old").mkdir()
    dirt.mkdir()
    (dirt / "k").symlink_to(tmp_path / "old")
    r = P.link_kit("k", samples=samples, dirt=dirt)
    assert r.action == "repointed"
    assert (dirt / "k").resolve() == (samples / "k").resolve()


def test_link_kit_refuses_to_clobber_real_dir(tmp_path):
    samples = tmp_path / "Samples"; dirt = tmp_path / "Dirt"
    (samples / "k").mkdir(parents=True)
    (dirt / "k").mkdir(parents=True)  # a real dir we didn't create
    r = P.link_kit("k", samples=samples, dirt=dirt)
    assert r.action == "skipped-real-dir"
    assert not (dirt / "k").is_symlink()


def test_link_kit_missing_source_raises(tmp_path):
    import pytest
    with pytest.raises(FileNotFoundError):
        P.link_kit("nope", samples=tmp_path / "Samples", dirt=tmp_path / "Dirt")
