"""Tests for rig — the RIG UP panel's state.

Every one of these is a bug that was actually made while writing the module, or
a promise the panel makes to someone reading it under pressure. Nothing here
runs systemctl, gig-up, or a launcher: the point is the classification, and a
test that boots SuperDirt is a test nobody will run.
"""
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import rig as R


# ── systemctl parsing ────────────────────────────────────────────────────────
def test_unit_states_parses_by_key_not_by_position(monkeypatch):
    """`systemctl show --value` emits properties in ITS order, not the caller's.

    The first version paired them positionally and silently swapped LoadState
    and ActiveState, so every loaded-but-inactive unit reported its state as
    'loaded'. Parsing by key is the only version that cannot drift.
    """
    out = ("Id=parvagues-sc.service\nLoadState=loaded\nActiveState=inactive\n"
           "Id=midi-autoconnect.service\nActiveState=active\nLoadState=loaded\n")

    class R_:
        stdout = out
    monkeypatch.setattr(R.subprocess, "run", lambda *a, **k: R_())
    st = R._unit_states(["parvagues-sc", "midi-autoconnect"])
    assert st["parvagues-sc"] == {"load": "loaded", "active": "inactive"}
    # note the reversed property order in the second block — must still be right
    assert st["midi-autoconnect"] == {"load": "loaded", "active": "active"}


def test_unit_states_survives_systemctl_being_absent(monkeypatch):
    def boom(*a, **k):
        raise OSError("no systemctl")
    monkeypatch.setattr(R.subprocess, "run", boom)
    assert R._unit_states(["parvagues-sc"]) == {}


# ── the classification the panel exists for ──────────────────────────────────
def _status_with(monkeypatch, states, running=()):
    monkeypatch.setattr(R, "_unit_states", lambda units: states)
    monkeypatch.setattr(R.L, "_cmdlines", lambda: [(n, n) for n in running])
    monkeypatch.setattr(R, "_lcxl", lambda: False)
    return {i["key"]: i for i in R.status()["items"]}


def test_active_unit_with_dead_process_is_broken_not_up(monkeypatch):
    """The 2026-08-01 failure: unit active, scsynth dead, silence on stage.

    `systemctl start` is a NO-OP on an active unit, so reporting this as merely
    'down' would send the reader to a button that cannot fix their problem. It
    has to say restart.
    """
    items = _status_with(monkeypatch,
                         {"parvagues-sc": {"load": "loaded", "active": "active"}},
                         running=())
    sc = items["parvagues-sc"]
    assert sc["state"] == "broken"
    assert "DEAD" in sc["detail"] and "restart" in sc["detail"]


def test_active_unit_with_live_process_is_up(monkeypatch):
    items = _status_with(monkeypatch,
                         {"parvagues-sc": {"load": "loaded", "active": "active"}},
                         running=("scsynth",))
    assert items["parvagues-sc"]["state"] == "up"


def test_missing_unit_is_absent_not_down(monkeypatch):
    """A thing you cannot start and a thing you can are different problems."""
    items = _status_with(monkeypatch,
                         {"parvagues-sc": {"load": "not-found", "active": "inactive"}})
    assert items["parvagues-sc"]["state"] == "absent"


def test_masked_unit_is_absent(monkeypatch):
    items = _status_with(monkeypatch,
                         {"parvagues-sc": {"load": "masked", "active": "inactive"}})
    assert items["parvagues-sc"]["state"] == "absent"


def test_unit_systemctl_could_not_answer_for_is_not_reported_up(monkeypatch):
    """An empty answer must never read as healthy — silence is not success."""
    items = _status_with(monkeypatch, {})
    assert items["parvagues-sc"]["state"] != "up"


# ── gear ─────────────────────────────────────────────────────────────────────
def test_gear_reads_conf(monkeypatch, tmp_path):
    c = tmp_path / "conf"
    c.write_text("MODE=cool\nGEAR=aggressive\nTRIM=quiet\nSTOCK_x=1\n")
    monkeypatch.setattr(R, "THERMAL_CONF", c)
    assert R.gear() == {"gear": "aggressive", "trim": "quiet"}


def test_gear_absent_conf_is_none_not_a_guess(monkeypatch, tmp_path):
    """No gearbox installed is a real state; the panel must not imply a gear."""
    monkeypatch.setattr(R, "THERMAL_CONF", tmp_path / "nope")
    assert R.gear() == {"gear": None, "trim": None}


def test_gear_off_reads_as_no_gear(monkeypatch, tmp_path):
    """`off` persists an empty MODE and GEAR=off — not a missing conf."""
    c = tmp_path / "conf"
    c.write_text("MODE=\nGEAR=off\nSTOCK_x=1\n")
    monkeypatch.setattr(R, "THERMAL_CONF", c)
    assert R.gear()["gear"] == "off"


# ── the converge job ─────────────────────────────────────────────────────────
def test_converge_refuses_to_double_start():
    """Two clicks must not run two converges at each other's SuperDirt."""
    job = R._Converge()
    job._running = True
    r = job.start()
    assert r["status"] == "already-running"


def test_converge_reports_a_missing_gig_up_instead_of_raising(monkeypatch):
    job = R._Converge()
    monkeypatch.setattr(R, "GIG_UP", Path("/definitely/not/here.sh"))
    monkeypatch.setattr(R.subprocess, "run",
                        lambda *a, **k: (_ for _ in ()).throw(OSError("nope")))
    job._run(launch_apps=False)
    s = job.state()
    assert s["running"] is False and s["rc"] == 127 and "not/here" in s["out"]


def test_converge_never_makes_sound(monkeypatch):
    """RIG UP must stay safe backstage: --converge, never --audio.

    gig-up's own contract is that the default gate is cold. The button inherits
    that promise, so the flag list is asserted rather than trusted.
    """
    seen = {}

    class R_:
        stdout, stderr, returncode = "", "", 0
    monkeypatch.setattr(R.subprocess, "run",
                        lambda argv, **k: (seen.update(argv=argv), R_())[1])
    R._Converge()._run(launch_apps=False)
    assert "--audio" not in seen["argv"]
    assert "--converge" in seen["argv"]
