#!/usr/bin/env python3
"""Tests: Anti-pattern registry loads active patterns correctly.

Verifies:
1. load_json returns [] for missing file (graceful)
2. load_json correctly loads anti_patterns_active.json
3. All real registry entries have required fields
4. search_anti_patterns filters only status=="active" entries
5. _load_anti_patterns_text returns only active entries
6. Inactive entries are excluded from both search and text load
"""
import json
import sys
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))


class TestLoadJsonGraceful(unittest.TestCase):
    """load_json graceful-fallback behaviour."""

    def test_missing_file_returns_empty_list(self):
        from pulse.core.brain_io import load_json
        result = load_json(Path("/nonexistent/path/anti_patterns_active.json"))
        self.assertEqual(result, [])

    def test_corrupted_file_returns_empty_list(self):
        from pulse.core.brain_io import load_json
        with tempfile.NamedTemporaryFile(suffix=".json", delete=False,
                                         mode="w", encoding="utf-8") as f:
            f.write("{ not valid json }")
            tmp = Path(f.name)
        try:
            result = load_json(tmp)
            self.assertIsInstance(result, list)
        finally:
            tmp.unlink(missing_ok=True)

    def test_valid_list_file_returns_list(self):
        from pulse.core.brain_io import load_json
        data = [{"id": "AP-001", "status": "active", "description": "test pattern"}]
        with tempfile.NamedTemporaryFile(suffix=".json", delete=False,
                                         mode="w", encoding="utf-8") as f:
            json.dump(data, f)
            tmp = Path(f.name)
        try:
            result = load_json(tmp)
            self.assertEqual(result, data)
        finally:
            tmp.unlink(missing_ok=True)


class TestRealRegistryStructure(unittest.TestCase):
    """Real anti_patterns_active.json has correct structure."""

    @classmethod
    def setUpClass(cls):
        from pulse.core.brain_utils import ANTI_PATTERNS_DIR
        cls.registry_file = ANTI_PATTERNS_DIR / "anti_patterns_active.json"

    def test_registry_file_exists(self):
        self.assertTrue(self.registry_file.exists(),
                        f"Registry file missing: {self.registry_file}")

    def test_registry_loads_as_list(self):
        from pulse.core.brain_io import load_json
        data = load_json(self.registry_file)
        self.assertIsInstance(data, list, "Registry must be a JSON list")

    def test_registry_not_empty(self):
        from pulse.core.brain_io import load_json
        data = load_json(self.registry_file)
        self.assertGreater(len(data), 0, "Registry should have at least one entry")

    def test_all_entries_have_required_fields(self):
        from pulse.core.brain_io import load_json
        required = {"id", "description", "status"}
        data = load_json(self.registry_file)
        for i, entry in enumerate(data):
            missing = required - set(entry.keys())
            self.assertFalse(missing,
                             f"Entry {i} missing fields {missing}: {entry.get('id', '?')}")

    def test_all_entries_have_active_status(self):
        """Every entry in the active registry must have status=='active'."""
        from pulse.core.brain_io import load_json
        data = load_json(self.registry_file)
        inactive = [e.get("id", f"idx-{i}") for i, e in enumerate(data)
                    if e.get("status") != "active"]
        self.assertFalse(inactive,
                         f"Non-active entries found in active registry: {inactive}")

    def test_entries_have_valid_severity(self):
        from pulse.core.brain_io import load_json
        valid_severities = {"low", "medium", "high"}
        data = load_json(self.registry_file)
        for entry in data:
            sev = entry.get("severity", "medium")
            self.assertIn(sev, valid_severities,
                          f"Invalid severity '{sev}' for entry {entry.get('id')}")


class TestSearchAntiPatternsFiltering(unittest.TestCase):
    """search_anti_patterns only returns status=='active' entries."""

    def _make_registry(self, entries):
        tmp = tempfile.NamedTemporaryFile(suffix=".json", delete=False,
                                          mode="w", encoding="utf-8")
        json.dump(entries, tmp)
        tmp.close()
        return Path(tmp.name)

    def tearDown(self):
        if hasattr(self, "_tmp") and self._tmp.exists():
            self._tmp.unlink(missing_ok=True)

    def test_only_active_entries_returned(self):
        import pulse.brain.search_sources.search_sources_core as ssc

        entries = [
            {"id": "AP-001", "status": "active", "description": "avoid sys.exit in library",
             "rule": "NEVER: sys.exit in library code", "keywords": ["sys.exit"],
             "severity": "high", "evidence_count": 3, "created": "2026-01-01T00:00:00+00:00"},
            {"id": "AP-002", "status": "retired", "description": "avoid sys.exit in library",
             "rule": "NEVER: retired rule", "keywords": ["retired"],
             "severity": "medium", "evidence_count": 1, "created": "2026-01-01T00:00:00+00:00"},
        ]
        self._tmp = self._make_registry(entries)
        # Temporarily rename to match expected filename
        target = self._tmp.parent / "anti_patterns_active.json"
        self._tmp.rename(target)
        self._tmp = target

        with patch.object(ssc, "ANTI_PATTERNS_DIR", self._tmp.parent):
            config = {"temporal_decay": False, "working_memory": False}
            results = ssc.search_anti_patterns("sys.exit library code", config)

        titles = [r["title"] for r in results]
        self.assertFalse(any("retired" in t.lower() for t in titles),
                         "Retired entry must not appear in results")
        if results:
            sources = [r["source"] for r in results]
            self.assertTrue(all(s in ("anti_patterns", "reflex_arc") for s in sources))

    def test_empty_registry_returns_empty_list(self):
        import pulse.brain.search_sources.search_sources_core as ssc
        import tempfile as _tmp

        with _tmp.TemporaryDirectory() as tmpdir:
            tmpdir = Path(tmpdir)
            (tmpdir / "anti_patterns_active.json").write_text("[]", encoding="utf-8")
            with patch.object(ssc, "ANTI_PATTERNS_DIR", tmpdir):
                config = {"temporal_decay": False, "working_memory": False}
                results = ssc.search_anti_patterns("anything", config)
        self.assertEqual(results, [])

    def test_missing_registry_returns_empty_list(self):
        import pulse.brain.search_sources.search_sources_core as ssc
        import tempfile as _tmp

        with _tmp.TemporaryDirectory() as tmpdir:
            tmpdir = Path(tmpdir)
            # Don't create the file — test graceful handling
            with patch.object(ssc, "ANTI_PATTERNS_DIR", tmpdir):
                config = {"temporal_decay": False, "working_memory": False}
                results = ssc.search_anti_patterns("anything", config)
        self.assertEqual(results, [])


class TestLoadAntiPatternsText(unittest.TestCase):
    """_load_anti_patterns_text returns only active entries."""

    def test_returns_no_active_message_when_empty(self):
        from pulse.admin.patterns import anti_pattern_similarity
        import tempfile as _tmp

        with _tmp.TemporaryDirectory() as tmpdir:
            ap_file = Path(tmpdir) / "anti_patterns_active.json"
            ap_file.write_text("[]", encoding="utf-8")
            with patch.object(anti_pattern_similarity, "ACTIVE_ANTI_PATTERNS", ap_file):
                text = anti_pattern_similarity._load_anti_patterns_text()
        self.assertEqual(text, "No active anti-patterns found.")

    def test_returns_no_active_entries_when_all_inactive(self):
        """When all entries are inactive, active filter yields empty string (not the fallback message).

        The fallback "No active anti-patterns found." only triggers when the file
        is empty or missing (load_json returns []). When the list is non-empty but
        all entries have status != "active", the join returns "".
        """
        from pulse.admin.patterns import anti_pattern_similarity
        import tempfile as _tmp

        entries = [{"id": "AP-001", "status": "retired",
                    "description": "retired rule", "severity": "medium"}]
        with _tmp.TemporaryDirectory() as tmpdir:
            ap_file = Path(tmpdir) / "anti_patterns_active.json"
            ap_file.write_text(json.dumps(entries), encoding="utf-8")
            with patch.object(anti_pattern_similarity, "ACTIVE_ANTI_PATTERNS", ap_file):
                text = anti_pattern_similarity._load_anti_patterns_text()
        # Retired entries must not appear in the output
        self.assertNotIn("retired rule", text)

    def test_active_entries_appear_in_text(self):
        from pulse.admin.patterns import anti_pattern_similarity
        import tempfile as _tmp

        entries = [
            {"id": "AP-001", "status": "active",
             "description": "never use sys.exit", "severity": "high"},
            {"id": "AP-002", "status": "retired",
             "description": "old retired rule", "severity": "low"},
        ]
        with _tmp.TemporaryDirectory() as tmpdir:
            ap_file = Path(tmpdir) / "anti_patterns_active.json"
            ap_file.write_text(json.dumps(entries), encoding="utf-8")
            with patch.object(anti_pattern_similarity, "ACTIVE_ANTI_PATTERNS", ap_file):
                text = anti_pattern_similarity._load_anti_patterns_text()

        self.assertIn("never use sys.exit", text)
        self.assertNotIn("old retired rule", text)

    def test_text_includes_severity(self):
        from pulse.admin.patterns import anti_pattern_similarity
        import tempfile as _tmp

        entries = [{"id": "AP-001", "status": "active",
                    "description": "active rule", "severity": "high"}]
        with _tmp.TemporaryDirectory() as tmpdir:
            ap_file = Path(tmpdir) / "anti_patterns_active.json"
            ap_file.write_text(json.dumps(entries), encoding="utf-8")
            with patch.object(anti_pattern_similarity, "ACTIVE_ANTI_PATTERNS", ap_file):
                text = anti_pattern_similarity._load_anti_patterns_text()

        self.assertIn("severity: high", text)


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