"""Pure-logic tests for the launcher registry (no processes spawned)."""
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import launchers as L


def test_registry_keys():
    assert {l["key"] for l in L.LAUNCHERS} == {"pulsar", "ardour", "qjackctl", "midimon"}
    assert {w["key"] for w in L.WEB} == {"foundry", "armada"}
    assert set(L.BY_KEY) >= {"pulsar", "ardour", "qjackctl", "midimon", "foundry", "armada"}


def test_which_first():
    assert L.which_first(["definitely-not-a-binary-xyz"]) is None
    assert L.which_first(["definitely-not-xyz", "python3"]).endswith("python3")


def test_snapshot_shape():
    s = L.snapshot()
    assert set(s) == {"web", "apps"}
    for it in s["apps"]:
        assert {"key", "name", "blurb", "kind", "available", "running", "terminal"} <= set(it)
        assert it["kind"] == "app"
    for it in s["web"]:
        assert it["kind"] == "web" and it["url"].startswith("http")


def test_launch_unknown():
    r = L.launch("nope")
    assert r["ok"] is False and r["status"] == "unknown"


def test_port_open_false_on_dead_port():
    assert L._port_open(59999) is False  # nothing should be listening here


def test_web_launch_already_running_opens(monkeypatch):
    # pretend the port is up → must NOT spawn, just report the url to open
    monkeypatch.setattr(L, "_port_open", lambda *a, **k: True)
    r = L.launch("foundry")
    assert r["ok"] is True and r["status"] == "already-running"
    assert r["url"].endswith(":8765/")


# --------------------------------------------------------------------------- #
# is_running: forkless, and no longer matching the whole world (#69)
# --------------------------------------------------------------------------- #

def test_cmdlines_returns_argv0_and_the_full_line():
    rows = L._cmdlines()
    assert rows, "no processes seen — the /proc scan is broken"
    for argv0, line in rows[:50]:
        assert "/" not in argv0          # basename only
        assert isinstance(line, str)


def test_an_exe_pattern_matches_the_executable_not_a_mention_of_it():
    """The bug: `pgrep -f ardour` matched the `tidal-ardour-autoroute.sh` helper AND
    any SHELL whose command line merely mentioned the word, so the tray could report
    Ardour up while it was down. Reporting gear up when it is down is worse than
    reporting nothing, because it is the state you act on."""
    spec = L.BY_KEY["ardour"]
    lines = [("bash", "bash /home/x/.local/bin/tidal-ardour-autoroute.sh"),
             ("zsh", "zsh -c 'grep ardour something'")]
    assert L.is_running(spec, lines) is False
    lines.append(("ardour-9.2.0", "/usr/bin/ardour-9.2.0 /home/x/Tidal Live.ardour"))
    assert L.is_running(spec, lines) is True


def test_the_ardour_exe_pattern_matches_a_version_suffixed_binary():
    """The installed binary is `ardour-9.2.0`, not `ardour` — a bare `ardour$` match
    would have reported it as down forever."""
    spec = L.BY_KEY["ardour"]
    for exe in ("ardour", "ardour9", "ardour-9.2.0", "Ardour", "ardour-10.0.1"):
        assert L.is_running(spec, [(exe, f"/usr/bin/{exe}")]) is True, exe


def test_a_session_filename_ending_in_dot_ardour_is_not_a_running_ardour():
    spec = L.BY_KEY["ardour"]
    assert L.is_running(spec, [("cp", "cp 'Tidal Live.ardour' /tmp")]) is False


def test_an_interpreted_tool_still_matches_on_the_full_line():
    """midimon runs as `python3 .../midimon.py`, so argv[0] is python3 and the real
    identity is the script path. That is the one case full-line matching is for."""
    spec = L.BY_KEY["midimon"]
    assert spec.get("exe") is None and spec.get("pgrep")
    assert L.is_running(spec, [("python3", "python3 /x/tools/bridge/midimon.py")]) is True
    assert L.is_running(spec, [("python3", "python3 /x/other.py")]) is False


def test_is_running_is_false_for_a_spec_with_no_pattern():
    assert L.is_running({"key": "x"}, [("anything", "anything")]) is False


def test_is_running_survives_a_broken_pattern():
    assert L.is_running({"exe": "([unclosed"}, [("x", "x")]) is False


def test_snapshot_scans_proc_once_for_all_apps(monkeypatch):
    """It used to fork one `pgrep` PER launcher, and snapshot() is called by both the
    web Bridge's poll and the tray menu. A fork per item per refresh is the shape of
    bug that made the LED daemon lag."""
    calls = []
    real = L._cmdlines
    monkeypatch.setattr(L, "_cmdlines", lambda: (calls.append(1), real())[1])
    L.snapshot()
    assert len(calls) == 1


def test_is_running_spawns_no_subprocess(monkeypatch):
    def boom(*a, **k):
        raise AssertionError("is_running must not fork")
    monkeypatch.setattr(L.subprocess, "run", boom)
    monkeypatch.setattr(L.subprocess, "Popen", boom)
    L.snapshot()


# --------------------------------------------------------------------------- #
# the session it opens
# --------------------------------------------------------------------------- #

def test_ardour_opens_the_LIVE_session_not_the_archive():
    """"Tidal Live" is the performing session; "Tidal Multi" is the older archive of
    per-orbit recordings. This entry pointed at the ARCHIVE, so the tray's Ardour
    button opened the wrong session and any faders touched there were the wrong ones.
    Auditing that same archive as if it were live already produced a confidently
    wrong fader report on 2026-07-28."""
    assert L.ARDOUR_SESSION.name == "Tidal Live.ardour"
    assert "Tidal Multi" not in str(L.ARDOUR_SESSION)


def test_ardour_candidates_include_the_installed_major_version():
    """`ardour9` was missing, so this entry could never start the installed Ardour —
    it reported "unavailable" while Ardour was running."""
    cands = L.BY_KEY["ardour"]["candidates"]
    assert "ardour9" in cands
    assert cands.index("ardour9") < cands.index("ardour8")   # newest first
