"""100% compliance test for Allegro Commit-or-Abort (M2, Epic #842)."""

import json
import os
import sys
import tempfile
import time
import unittest
from datetime import datetime, timezone, timedelta

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

import cycle_guard as cg


class TestCycleGuard(unittest.TestCase):
    def setUp(self):
        self.tmpdir = tempfile.TemporaryDirectory()
        self.state_path = os.path.join(self.tmpdir.name, "cycle_state.json")
        cg.STATE_PATH = self.state_path

    def tearDown(self):
        self.tmpdir.cleanup()
        cg.STATE_PATH = cg.DEFAULT_STATE

    def test_load_empty_state(self):
        state = cg.load_state(self.state_path)
        self.assertEqual(state["status"], "complete")
        self.assertIsNone(state["cycle_id"])

    def test_start_cycle(self):
        state = cg.start_cycle("M2: Commit-or-Abort", path=self.state_path)
        self.assertEqual(state["status"], "in_progress")
        self.assertEqual(state["target"], "M2: Commit-or-Abort")
        self.assertIsNotNone(state["cycle_id"])

    def test_start_slice_requires_in_progress(self):
        with self.assertRaises(RuntimeError):
            cg.start_slice("test", path=self.state_path)

    def test_slice_lifecycle(self):
        cg.start_cycle("test", path=self.state_path)
        cg.start_slice("gather", path=self.state_path)
        state = cg.load_state(self.state_path)
        self.assertEqual(len(state["slices"]), 1)
        self.assertEqual(state["slices"][0]["name"], "gather")
        self.assertEqual(state["slices"][0]["status"], "in_progress")

        cg.end_slice(status="complete", artifact="artifact.txt", path=self.state_path)
        state = cg.load_state(self.state_path)
        self.assertEqual(state["slices"][0]["status"], "complete")
        self.assertEqual(state["slices"][0]["artifact"], "artifact.txt")
        self.assertIsNotNone(state["slices"][0]["ended_at"])

    def test_commit_cycle(self):
        cg.start_cycle("test", path=self.state_path)
        cg.start_slice("work", path=self.state_path)
        cg.end_slice(path=self.state_path)
        proof = {"files": ["a.py"]}
        state = cg.commit_cycle(proof=proof, path=self.state_path)
        self.assertEqual(state["status"], "complete")
        self.assertEqual(state["proof"], proof)
        self.assertIsNotNone(state["completed_at"])

    def test_commit_without_in_progress_fails(self):
        with self.assertRaises(RuntimeError):
            cg.commit_cycle(path=self.state_path)

    def test_abort_cycle(self):
        cg.start_cycle("test", path=self.state_path)
        cg.start_slice("work", path=self.state_path)
        state = cg.abort_cycle("manual abort", path=self.state_path)
        self.assertEqual(state["status"], "aborted")
        self.assertEqual(state["abort_reason"], "manual abort")
        self.assertIsNotNone(state["aborted_at"])
        self.assertEqual(state["slices"][-1]["status"], "aborted")

    def test_slice_timeout_true(self):
        cg.start_cycle("test", path=self.state_path)
        cg.start_slice("work", path=self.state_path)
        # Manually backdate slice start to 11 minutes ago
        state = cg.load_state(self.state_path)
        old = (datetime.now(timezone.utc) - timedelta(minutes=11)).isoformat()
        state["slices"][0]["started_at"] = old
        cg.save_state(state, self.state_path)
        self.assertTrue(cg.check_slice_timeout(max_minutes=10, path=self.state_path))

    def test_slice_timeout_false(self):
        cg.start_cycle("test", path=self.state_path)
        cg.start_slice("work", path=self.state_path)
        self.assertFalse(cg.check_slice_timeout(max_minutes=10, path=self.state_path))

    def test_resume_or_abort_keeps_fresh_cycle(self):
        cg.start_cycle("test", path=self.state_path)
        state = cg.resume_or_abort(path=self.state_path)
        self.assertEqual(state["status"], "in_progress")

    def test_resume_or_abort_aborts_stale_cycle(self):
        cg.start_cycle("test", path=self.state_path)
        # Backdate start to 31 minutes ago
        state = cg.load_state(self.state_path)
        old = (datetime.now(timezone.utc) - timedelta(minutes=31)).isoformat()
        state["started_at"] = old
        cg.save_state(state, self.state_path)
        state = cg.resume_or_abort(path=self.state_path)
        self.assertEqual(state["status"], "aborted")
        self.assertIn("crash recovery", state["abort_reason"])

    def test_slice_duration_minutes(self):
        cg.start_cycle("test", path=self.state_path)
        cg.start_slice("work", path=self.state_path)
        # Backdate by 5 minutes
        state = cg.load_state(self.state_path)
        old = (datetime.now(timezone.utc) - timedelta(minutes=5)).isoformat()
        state["slices"][0]["started_at"] = old
        cg.save_state(state, self.state_path)
        mins = cg.slice_duration_minutes(path=self.state_path)
        self.assertAlmostEqual(mins, 5.0, delta=0.5)

    def test_cli_resume_prints_status(self):
        cg.start_cycle("test", path=self.state_path)
        rc = cg.main(["resume"])
        self.assertEqual(rc, 0)

    def test_cli_check_timeout(self):
        cg.start_cycle("test", path=self.state_path)
        cg.start_slice("work", path=self.state_path)
        state = cg.load_state(self.state_path)
        old = (datetime.now(timezone.utc) - timedelta(minutes=11)).isoformat()
        state["slices"][0]["started_at"] = old
        cg.save_state(state, self.state_path)
        rc = cg.main(["check"])
        self.assertEqual(rc, 1)

    def test_cli_check_ok(self):
        cg.start_cycle("test", path=self.state_path)
        cg.start_slice("work", path=self.state_path)
        rc = cg.main(["check"])
        self.assertEqual(rc, 0)


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