"""
tests/test_sentinel_persistence.py — Unit tests for fw_server.persistence sentinel functions.

Coverage:
  - save_sentinel_state(): atomic write, guard on empty, rotation
  - restore_sentinel_state(): valid restore, corrupt file, missing file
  - get_sentinel_persistence_info(): diagnostics with/without state dir
"""

from __future__ import annotations

import json
import os
import tempfile
import unittest
from pathlib import Path
from unittest.mock import MagicMock

from fw_server.persistence import (
    SENTINEL_FILENAME,
    get_sentinel_persistence_info,
    restore_sentinel_state,
    save_sentinel_state,
)


class _MockSentinel:
    """Minimal stub standing in for SentinelEngine."""

    def __init__(self, incident_count: int = 0, state: dict | None = None):
        self.incident_count = incident_count
        self._state = state or {"incidents": [], "pattern_rules": [], "co_articulation": []}
        self._imported: dict | None = None

    def export_state(self) -> dict:
        return self._state

    def import_state(self, state: dict) -> None:
        self._imported = state


class TestSaveSentinelState(unittest.TestCase):
    """save_sentinel_state()."""

    def setUp(self):
        self.td = tempfile.mkdtemp()
        self.state_dir = Path(self.td)

    def tearDown(self):
        # Cleanup
        import shutil
        shutil.rmtree(self.td, ignore_errors=True)

    def test_skips_when_empty(self):
        sentinel = _MockSentinel(incident_count=0)
        result = save_sentinel_state(self.state_dir, sentinel)
        self.assertIsNone(result)
        self.assertFalse((self.state_dir / SENTINEL_FILENAME).exists())

    def test_saves_when_nonempty(self):
        sentinel = _MockSentinel(
            incident_count=2,
            state={"incidents": [{"id": "INC-001"}, {"id": "INC-002"}], "pattern_rules": []},
        )
        result = save_sentinel_state(self.state_dir, sentinel)
        self.assertIsNotNone(result)
        fp = self.state_dir / SENTINEL_FILENAME
        self.assertTrue(fp.exists())
        data = json.loads(fp.read_text(encoding="utf-8"))
        self.assertEqual(len(data["incidents"]), 2)

    def test_rotation_creates_backup(self):
        sentinel = _MockSentinel(incident_count=1, state={"incidents": [{"id": "x"}], "pattern_rules": []})
        # Save twice → first becomes backup
        save_sentinel_state(self.state_dir, sentinel)
        save_sentinel_state(self.state_dir, sentinel)
        backups = list(self.state_dir.glob("sentinel_state.*.json"))
        self.assertGreaterEqual(len(backups), 1)


class TestRestoreSentinelState(unittest.TestCase):
    """restore_sentinel_state()."""

    def setUp(self):
        self.td = tempfile.mkdtemp()
        self.state_dir = Path(self.td)

    def tearDown(self):
        import shutil
        shutil.rmtree(self.td, ignore_errors=True)

    def test_returns_false_when_no_file(self):
        sentinel = _MockSentinel()
        self.assertFalse(restore_sentinel_state(self.state_dir, sentinel))

    def test_restores_valid_checkpoint(self):
        state = {"incidents": [{"id": "INC-001"}], "pattern_rules": []}
        fp = self.state_dir / SENTINEL_FILENAME
        fp.write_text(json.dumps(state), encoding="utf-8")

        sentinel = _MockSentinel()
        result = restore_sentinel_state(self.state_dir, sentinel)
        self.assertTrue(result)
        self.assertIsNotNone(sentinel._imported)
        self.assertEqual(sentinel._imported["incidents"][0]["id"], "INC-001")

    def test_corrupt_file(self):
        fp = self.state_dir / SENTINEL_FILENAME
        fp.write_text("{invalid json!", encoding="utf-8")

        sentinel = _MockSentinel()
        self.assertFalse(restore_sentinel_state(self.state_dir, sentinel))


class TestGetSentinelPersistenceInfo(unittest.TestCase):
    """get_sentinel_persistence_info()."""

    def test_no_state_dir(self):
        info = get_sentinel_persistence_info(None)
        self.assertFalse(info["sentinel_persistence_enabled"])
        self.assertIn("reason", info)

    def test_no_checkpoint(self):
        with tempfile.TemporaryDirectory() as td:
            info = get_sentinel_persistence_info(Path(td))
            self.assertTrue(info["sentinel_persistence_enabled"])
            self.assertFalse(info["sentinel_checkpoint_exists"])

    def test_with_checkpoint(self):
        with tempfile.TemporaryDirectory() as td:
            sd = Path(td)
            state = {"incidents": [{"id": "INC-1"}], "pattern_rules": [{"id": "PR-1"}]}
            (sd / SENTINEL_FILENAME).write_text(json.dumps(state), encoding="utf-8")
            info = get_sentinel_persistence_info(sd)
            self.assertTrue(info["sentinel_checkpoint_exists"])
            self.assertEqual(info["sentinel_incident_count"], 1)
            self.assertEqual(info["sentinel_pattern_rule_count"], 1)
            self.assertIn("sentinel_checkpoint_size_bytes", info)
            self.assertIn("sentinel_checkpoint_modified", info)

    def test_backup_count(self):
        with tempfile.TemporaryDirectory() as td:
            sd = Path(td)
            (sd / SENTINEL_FILENAME).write_text("{}", encoding="utf-8")
            # Create fake backups
            (sd / "sentinel_state.1.json").write_text("{}", encoding="utf-8")
            (sd / "sentinel_state.2.json").write_text("{}", encoding="utf-8")
            info = get_sentinel_persistence_info(sd)
            self.assertEqual(info["sentinel_n_backups"], 2)


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