from bubblecam import state_lib
from bubblecam.hardware.gpio_switch import RecordSwitch, requested_from_grounded
from conftest import load_bin_module

record_switch = load_bin_module("record_switch")


def cfg(mode="sbs_mjpeg"):
    return {"camera": {"mode": mode}}


def test_camera_systemd_units_single_vs_dual():
    assert record_switch.camera_systemd_units(cfg("sbs_mjpeg")) == [
        "bubblecam-camera@primary.service"
    ]
    assert record_switch.camera_systemd_units(cfg("dual_node_h264")) == [
        "bubblecam-camera@left.service",
        "bubblecam-camera@right.service",
    ]


def test_polarity_closed_means_record_by_default():
    # Operator preference: flip the switch on (short pin to GND) to record.
    assert requested_from_grounded(grounded=True, record_when_grounded=True) is True
    assert requested_from_grounded(grounded=False, record_when_grounded=True) is False


def test_polarity_inverted_for_fail_safe_deployments():
    # record_when_grounded=false: an open/broken switch keeps recording.
    assert requested_from_grounded(grounded=True, record_when_grounded=False) is False
    assert requested_from_grounded(grounded=False, record_when_grounded=False) is True


def test_sim_switch_defaults_to_record(sim_env):
    assert RecordSwitch().recording_requested() is True


def test_sim_switch_reads_stop(sim_env, monkeypatch):
    monkeypatch.setenv("BUBBLECAM_SIM_SWITCH", "stop")
    assert RecordSwitch().recording_requested() is False


def test_set_unit_and_reconcile_in_sim_mode_do_not_raise(sim_env):
    # In BUBBLECAM_SIM=1 these only print -- must not shell out to a real
    # systemctl (which doesn't exist on a dev machine).
    units = record_switch.camera_systemd_units(cfg())
    record_switch.set_unit(units[0], True)
    # sim unit_is_active is always False, so reconcile(True) starts, and
    # reconcile(False) is a no-op -- both paths exercised.
    record_switch.reconcile(units, True)
    record_switch.reconcile(units, False)


class ScriptedSwitch:
    """Fake RecordSwitch returning a scripted sequence of readings."""

    def __init__(self, readings):
        self._readings = list(readings)

    def recording_requested(self):
        return self._readings.pop(0)


def test_poll_once_debounces_a_single_blip(monkeypatch):
    monkeypatch.setattr(record_switch, "unit_is_active", lambda u: True)
    calls = []
    monkeypatch.setattr(record_switch, "set_unit", lambda u, a: calls.append(a))

    # One stray "stop" reading between steady "record" readings: must not act.
    switch = ScriptedSwitch([False, True])
    acted, pending = record_switch.poll_once(switch, ["u"], acted_on=True, pending=True)
    assert acted is True
    acted, pending = record_switch.poll_once(switch, ["u"], acted, pending)
    assert acted is True
    assert calls == []  # unit active, switch record -> never touched


def test_poll_once_acts_on_two_consecutive_readings(monkeypatch):
    monkeypatch.setattr(record_switch, "unit_is_active", lambda u: True)
    calls = []
    monkeypatch.setattr(record_switch, "set_unit", lambda u, a: calls.append(a))

    switch = ScriptedSwitch([False, False])
    acted, pending = record_switch.poll_once(switch, ["u"], acted_on=True, pending=True)
    assert acted is True  # first differing read: debounce holds
    acted, pending = record_switch.poll_once(switch, ["u"], acted, pending)
    assert acted is False
    assert calls == [False]  # stop issued


def test_poll_once_corrects_drift_every_poll(monkeypatch):
    """Regression guard for the stuck-recording bug: a unit (re)started
    behind the daemon's back -- e.g. systemd's Restart=on-failure firing
    just after a stop -- must be stopped again on the very next poll, not
    only on the next switch transition."""
    monkeypatch.setattr(record_switch, "unit_is_active", lambda u: True)  # unit running...
    calls = []
    monkeypatch.setattr(record_switch, "set_unit", lambda u, a: calls.append(a))

    switch = ScriptedSwitch([False, False, False])  # ...switch steadily says stop
    acted, pending = True, True
    for _ in range(3):
        acted, pending = record_switch.poll_once(switch, ["u"], acted, pending)
    # Acted after debounce (poll 2), then kept correcting on poll 3.
    assert calls == [False, False]


def test_switch_state_integrates_with_recording_requested(sim_env):
    state_lib.write_state("record_switch", "running", recording_requested=False)
    assert state_lib.recording_requested() is False
