"""Tests for hooks/session_start.py. Run: python3 tests/hook_test.py"""

import io
import json
import subprocess
import sys
import unittest
from datetime import date
from pathlib import Path
from tempfile import TemporaryDirectory

PLUGIN_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PLUGIN_ROOT / "hooks"))

from session_start import (  # noqa: E402
    ABSENT_NOTE,
    PRESENT_NOTE,
    StateDir,
    adr_system_present,
    decide,
    find_project_root,
    run,
)

TODAY = date(2026, 7, 3)
YESTERDAY = date(2026, 7, 2)


class DecideTest(unittest.TestCase):
    def test_present_injects_context_naming_both_surfaces(self):
        outcome, note = decide(True, False, None, TODAY)
        self.assertEqual("context", outcome)
        self.assertIn("/os-adr:create", note)
        self.assertIn("/os-adr:find", note)

    def test_present_wins_even_if_suppressed(self):
        self.assertEqual("context", decide(True, True, None, TODAY)[0])

    def test_absent_unsuppressed_banners_with_init_and_migrate(self):
        outcome, note = decide(False, False, None, TODAY)
        self.assertEqual("banner", outcome)
        self.assertIn("/os-adr:init", note)
        self.assertIn("/os-adr:migrate", note)

    def test_absent_suppressed_is_silent(self):
        self.assertEqual(("silent", None), decide(False, True, None, TODAY))

    def test_absent_reminded_today_is_silent(self):
        self.assertEqual(("silent", None), decide(False, False, TODAY, TODAY))

    def test_absent_reminded_yesterday_banners_again(self):
        self.assertEqual("banner", decide(False, False, YESTERDAY, TODAY)[0])


class RunTest(unittest.TestCase):
    def project(self, tmp, with_adr=False):
        root = Path(tmp)
        (root / ".git").mkdir()
        if with_adr:
            (root / "docs" / "adr").mkdir(parents=True)
            (root / "docs" / "adr" / "README.md").write_text("index")
        return root

    def test_present_emits_additional_context(self):
        with TemporaryDirectory() as tmp:
            root = self.project(tmp, with_adr=True)
            out = io.StringIO()
            self.assertEqual("context", run(root, TODAY, out))
            payload = json.loads(out.getvalue())
            self.assertEqual(
                PRESENT_NOTE,
                payload["hookSpecificOutput"]["additionalContext"],
            )
            self.assertFalse((root / ".os-adr").exists(), "present case must not create state")

    def test_absent_emits_banner_and_stamps_snooze(self):
        with TemporaryDirectory() as tmp:
            root = self.project(tmp)
            out = io.StringIO()
            self.assertEqual("banner", run(root, TODAY, out))
            self.assertEqual(ABSENT_NOTE, json.loads(out.getvalue())["systemMessage"])
            self.assertEqual(
                TODAY.isoformat(), (root / ".os-adr" / "last_reminded").read_text()
            )

    def test_second_run_same_day_is_silent(self):
        with TemporaryDirectory() as tmp:
            root = self.project(tmp)
            run(root, TODAY, io.StringIO())
            out = io.StringIO()
            self.assertEqual("silent", run(root, TODAY, out))
            self.assertEqual("", out.getvalue())

    def test_next_day_banners_again(self):
        with TemporaryDirectory() as tmp:
            root = self.project(tmp)
            run(root, YESTERDAY, io.StringIO())
            self.assertEqual("banner", run(root, TODAY, io.StringIO()))

    def test_suppressed_project_stays_silent(self):
        with TemporaryDirectory() as tmp:
            root = self.project(tmp)
            (root / ".os-adr").mkdir()
            (root / ".os-adr" / "suppress").write_text("")
            out = io.StringIO()
            self.assertEqual("silent", run(root, TODAY, out))
            self.assertEqual("", out.getvalue())

    def test_corrupt_snooze_stamp_is_treated_as_unset(self):
        with TemporaryDirectory() as tmp:
            root = self.project(tmp)
            (root / ".os-adr").mkdir()
            (root / ".os-adr" / "last_reminded").write_text("not-a-date")
            self.assertEqual("banner", run(root, TODAY, io.StringIO()))


class ProjectRootTest(unittest.TestCase):
    def test_walks_up_to_git_root(self):
        with TemporaryDirectory() as tmp:
            root = Path(tmp)
            (root / ".git").mkdir()
            nested = root / "a" / "b"
            nested.mkdir(parents=True)
            self.assertEqual(root, find_project_root(nested))

    def test_none_outside_any_git_project(self):
        with TemporaryDirectory() as tmp:
            self.assertIsNone(find_project_root(Path(tmp)))


class PythonRubyAgreementTest(unittest.TestCase):
    """Shared fixture tree: the Python existence check and Ruby
    Adr::Repository#exists? must give the same answer (design D3 risk)."""

    def ruby_exists(self, root):
        script = (
            f'require "{PLUGIN_ROOT}/lib/adr"; '
            f'puts Adr::Repository.new(root: {str(root)!r}).exists?'
        )
        out = subprocess.run(
            ["ruby", "-e", script], capture_output=True, text=True, check=True
        )
        return out.stdout.strip() == "true"

    def assert_agreement(self, root, expected):
        self.assertEqual(expected, adr_system_present(root), "python check")
        self.assertEqual(expected, self.ruby_exists(root), "ruby check")

    def test_agreement_across_fixture_states(self):
        with TemporaryDirectory() as tmp:
            root = Path(tmp)
            self.assert_agreement(root, False)          # nothing
            (root / "docs" / "adr").mkdir(parents=True)
            self.assert_agreement(root, False)          # dir, no index
            (root / "docs" / "adr" / "README.md").write_text("index")
            self.assert_agreement(root, True)           # dir + index


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