import subprocess
import sys
import tempfile
import threading
import time
import unittest
from pathlib import Path
from queue import Empty, Queue

sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))

from agent_guard.events import fswatch


def drain(queue):
    reasons = []
    while True:
        try:
            reasons.append(queue.get_nowait().reason)
        except Empty:
            return reasons


class FswatchTests(unittest.TestCase):
    def watch(self, directory, act):
        queue = Queue()
        stop = threading.Event()
        thread = threading.Thread(target=fswatch.follow, args=(queue, [str(directory)], stop), daemon=True)
        thread.start()
        time.sleep(0.3)
        act()
        time.sleep(fswatch.SETTLE_SECONDS * 3)
        stop.set()
        thread.join(timeout=5)
        return drain(queue)

    def test_atomic_symlink_replace_reports_only_the_surviving_name(self):
        # `ln -sfn` links a randomly named entry and renames it over the target. Reporting
        # that transient name is what produced 190 identical alerts for one deploy loop.
        with tempfile.TemporaryDirectory() as td:
            directory = Path(td)
            (directory / "target-a").touch()
            (directory / "target-b").touch()
            (directory / "unit.service").symlink_to("target-a")
            reasons = self.watch(
                directory,
                lambda: subprocess.run(["ln", "-sfn", "target-b", "unit.service"], cwd=directory, check=True),
            )
        self.assertEqual(reasons, [f"Sensitive path changed: {directory / 'unit.service'}"])

    def test_new_file_that_survives_is_reported(self):
        with tempfile.TemporaryDirectory() as td:
            directory = Path(td)
            reasons = self.watch(directory, lambda: (directory / "authorized_keys").write_text("ssh-ed25519 AAAA\n"))
        self.assertEqual(reasons, [f"Sensitive path changed: {directory / 'authorized_keys'}"])

    def test_new_symlink_dropped_into_the_directory_is_reported(self):
        with tempfile.TemporaryDirectory() as td:
            directory = Path(td)
            reasons = self.watch(directory, lambda: (directory / "payload.service").symlink_to("/dev/null"))
        self.assertEqual(reasons, [f"Sensitive path changed: {directory / 'payload.service'}"])

    def test_entry_gone_before_it_settles_is_not_reported(self):
        with tempfile.TemporaryDirectory() as td:
            directory = Path(td)

            def act():
                scratch = directory / "scratch"
                scratch.write_text("x")
                scratch.unlink()

            self.assertEqual(self.watch(directory, act), [])


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