#!/usr/bin/env python3
"""ENFORCE is the master switch: with it unset no reaper path may signal anything.

Signaling is stubbed at every primitive the reaper can reach -- lib.reap_tree,
lib.kill_ladder and os.kill -- so a future kill path added to main() fails here
too, not only the four that exist today.
"""

from __future__ import annotations

import importlib.util
import os
import unittest
import unittest.mock

_BIN = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "bin")
_SPEC = importlib.util.spec_from_file_location(
    "agent_reaper", os.path.join(_BIN, "agent-reaper.py"))
agent_reaper = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(agent_reaper)
lib = agent_reaper.lib

VERDICTS = ("BLOCKED_TURN", "ORPHANED_SIDECAR", "ORPHANED_RUNTIME",
            "ENFORCE_ELIGIBLE", "TEST_ORPHAN", "WARN")
RESULTS = [{"verdict": v, "pid": 4242, "child_pid": 4243} for v in VERDICTS]
VERIFIED = {"verdict": "ENFORCE_ELIGIBLE", "pid": 4242, "child_pid": 4243,
            "child_age_s": 1, "child_cmdline": "sleep", "age_s": 1,
            "cmdline": "sleep", "trigger_pid": 1, "trigger_cmdline": "sleep"}


class EnforceMasterSwitchTests(unittest.TestCase):
    def setUp(self) -> None:
        self.signals = []
        patches = [
            unittest.mock.patch.object(agent_reaper, "scan_once", lambda: RESULTS),
            unittest.mock.patch.object(lib, "load_state", lambda: {}),
            unittest.mock.patch.object(lib, "log_event", lambda event: None),
            unittest.mock.patch.object(os, "kill", self._record("os.kill")),
            unittest.mock.patch.object(lib, "reap_tree", self._record("reap_tree")),
            unittest.mock.patch.object(lib, "kill_ladder", self._record("kill_ladder")),
        ]
        for name in ("verify_blocked_turn_before_kill", "verify_sidecar_before_kill",
                     "verify_orphaned_runtime_before_kill", "verify_live_before_kill",
                     "verify_test_orphan_before_kill"):
            if hasattr(lib, name):
                patches.append(unittest.mock.patch.object(
                    lib, name, lambda *a, **k: dict(VERIFIED)))
        for patch in patches:
            patch.start()
            self.addCleanup(patch.stop)

    def _record(self, name):
        def recorder(*args, **kwargs):
            self.signals.append(name)
            return "recorded"
        return recorder

    def _run(self, enforce) -> None:
        env = {k: v for k, v in os.environ.items() if k != "ENFORCE"}
        if enforce is not None:
            env["ENFORCE"] = enforce
        with unittest.mock.patch.dict(os.environ, env, clear=True):
            agent_reaper.main()

    def test_unset_signals_nothing(self) -> None:
        self._run(None)
        self.assertEqual(self.signals, [])

    def test_zero_signals_nothing(self) -> None:
        self._run("0")
        self.assertEqual(self.signals, [])

    def test_non_numeric_signals_nothing(self) -> None:
        self._run("true")
        self.assertEqual(self.signals, [])

    def test_enabled_reaches_every_kill_path(self) -> None:
        self._run("1")
        self.assertEqual(self.signals, ["reap_tree", "reap_tree", "reap_tree",
                                        "kill_ladder"])


if __name__ == "__main__":
    unittest.main(verbosity=2)
