"""Tests for gearbox — the client both gear faces speak.

Two things are being defended here, and neither is arithmetic:

  1. THE SUDOERS CONTRACT. Switching goes through exact-args NOPASSWD rules, so
     the argv this module builds is a security boundary, not a convenience. A
     stray word, a joined "--trim cool", or an unvalidated key turns a working
     click into an opaque sudo refusal — or, worse, widens what the rule matches.

  2. THE HONEST-UNSET RULE. "no gear has ever been engaged" and "the gear is off"
     are different states. Collapsing them is what made `off` have to become an
     explicit gear in the first place, and a UI that guesses here lies about who
     owns the CPU.
"""
import sys
from pathlib import Path

import pytest

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


def conf(tmp_path, text):
    p = tmp_path / "thermal-mode.conf"
    p.write_text(text)
    return p


# ── reading state ────────────────────────────────────────────────────────────
def test_reads_gear_and_trim(tmp_path):
    c = conf(tmp_path, "MODE=cool\nBIOS_PROFILE=cool\nGEAR=standard\nTRIM=cool\n")
    s = GB.state(c)
    assert (s["gear"], s["trim"], s["mode"], s["bios"]) == ("standard", "cool", "cool", "cool")


def test_stock_rapl_keys_do_not_confuse_the_parser(tmp_path):
    """The conf also carries the STOCK_* values `off` restores from. They share
    the file with GEAR/TRIM and must be inert to every reader of it."""
    c = conf(tmp_path, "GEAR=aggressive\nTRIM=quiet\n"
                       "STOCK_intel_rapl_0_PL1=12000000\n"
                       "STOCK_intel_rapl_mmio_0_PL2=135000000\n")
    s = GB.state(c)
    assert (s["gear"], s["trim"]) == ("aggressive", "quiet")


def test_absent_conf_is_unset_not_off(tmp_path):
    """No gearbox installed here is a real state. Reporting it as `off` would
    claim a deliberate decision nobody made."""
    s = GB.state(tmp_path / "nope.conf")
    assert s["gear"] is None and s["trim"] is None


def test_off_is_a_gear_not_an_absence(tmp_path):
    s = GB.state(conf(tmp_path, "GEAR=off\n"))
    assert s["gear"] == "off"


def test_unknown_gear_in_conf_reads_as_none(tmp_path):
    """A conf written by a NEWER gearbox must not check a radio button that this
    UI cannot honour — better no selection than a wrong one."""
    s = GB.state(conf(tmp_path, "GEAR=ludicrous\nTRIM=cryogenic\n"))
    assert s["gear"] is None and s["trim"] is None


def test_empty_value_is_none(tmp_path):
    """`off` clears MODE= to an empty value rather than deleting the line."""
    s = GB.state(conf(tmp_path, "MODE=\nGEAR=off\n"))
    assert s["mode"] is None and s["gear"] == "off"


def test_comments_are_ignored(tmp_path):
    s = GB.state(conf(tmp_path, "# written by thermal-mode-apply\nGEAR=standard\n"))
    assert s["gear"] == "standard"


# ── the argv that sudoers has to match ───────────────────────────────────────
def test_gear_argv_is_non_interactive(monkeypatch):
    """`-n` is not optional: the tray has no tty, so an interactive sudo would
    hang a menu click forever instead of failing where someone can see it."""
    monkeypatch.setattr(GB, "HELPER", "/usr/local/sbin/thermal-mode-apply")
    assert GB.gear_argv("aggressive") == [
        "sudo", "-n", "/usr/local/sbin/thermal-mode-apply", "aggressive"]


def test_trim_argv_keeps_flag_and_value_as_two_words(monkeypatch):
    """The sudoers rule whitelists the PAIR `--trim cool`. Joining them into one
    argument matches nothing and fails as a permission error, which is the most
    misleading way this could break."""
    monkeypatch.setattr(GB, "HELPER", "/usr/local/sbin/thermal-mode-apply")
    assert GB.trim_argv("cool")[-2:] == ["--trim", "cool"]


@pytest.mark.parametrize("key", ["", "off; rm -rf /", "performance", None, "--boot"])
def test_unknown_gear_never_reaches_sudo(key):
    """Validate against the table, not against sudo. `performance` is a real
    helper verb and `--boot` is a real flag — neither is a gear, and neither
    should be reachable from a face that only knows about gears."""
    with pytest.raises(ValueError):
        GB.gear_argv(key)


@pytest.mark.parametrize("key", ["", "cold", None, "--boot"])
def test_unknown_trim_never_reaches_sudo(key):
    with pytest.raises(ValueError):
        GB.trim_argv(key)


def test_apply_refuses_when_helper_absent(monkeypatch):
    """No helper = say so, do not shell out. Otherwise the failure surfaces as a
    sudo error about a missing file, which reads like a permissions problem."""
    monkeypatch.setattr(GB, "HELPER", "/nonexistent/thermal-mode-apply")
    ok, msg = GB.apply("gear", "standard")
    assert not ok and "not installed" in msg


# ── labels the faces render ──────────────────────────────────────────────────
def test_label_falls_back_to_the_raw_key():
    """A gearbox newer than its UI must not render a blank row."""
    assert GB.label("gear", "ludicrous") == "ludicrous"
    assert GB.label("gear", None) == "—"


def test_summary_defaults_trim_but_never_defaults_gear(tmp_path, monkeypatch):
    """Trim has a real default in the engine (auto), so filling it in is honest.
    A gear does not: an unset gear means nobody is steering, and printing
    "standard" there would invent a decision."""
    monkeypatch.setattr(GB, "installed", lambda: True)
    assert GB.summary(conf(tmp_path, "GEAR=standard\n")) == "Standard · Auto"
    assert GB.summary(conf(tmp_path, "TRIM=cool\n")) == "unset · Cool"


def test_summary_says_not_installed(tmp_path, monkeypatch):
    monkeypatch.setattr(GB, "installed", lambda: False)
    assert GB.summary(tmp_path / "nope.conf") == "not installed"


# ── the vocabulary itself ────────────────────────────────────────────────────
def test_three_gears_and_three_trims_in_lever_order():
    """The count IS the design decision (GEARBOX.md): five flat modes asked the
    reader to believe quiet and cool were the same wish. If a fourth gear ever
    lands here, that decision is being revisited — deliberately, not by drift."""
    assert list(GB.GEARS) == ["off", "standard", "aggressive"]
    assert list(GB.TRIMS) == ["quiet", "auto", "cool"]
