"""Tests for the sovereignty perception cache (template matching).

Refs: #1261
"""

import json
from unittest.mock import patch

import numpy as np


class TestTemplate:
    """Tests for the Template dataclass."""

    def test_template_default_values(self):
        """Template dataclass has correct defaults."""
        from timmy.sovereignty.perception_cache import Template

        image = np.array([[1, 2], [3, 4]])
        template = Template(name="test_template", image=image)

        assert template.name == "test_template"
        assert np.array_equal(template.image, image)
        assert template.threshold == 0.85

    def test_template_custom_threshold(self):
        """Template can have custom threshold."""
        from timmy.sovereignty.perception_cache import Template

        image = np.array([[1, 2], [3, 4]])
        template = Template(name="test_template", image=image, threshold=0.95)

        assert template.threshold == 0.95


class TestCacheResult:
    """Tests for the CacheResult dataclass."""

    def test_cache_result_with_state(self):
        """CacheResult stores confidence and state."""
        from timmy.sovereignty.perception_cache import CacheResult

        result = CacheResult(confidence=0.92, state={"template_name": "test"})
        assert result.confidence == 0.92
        assert result.state == {"template_name": "test"}

    def test_cache_result_no_state(self):
        """CacheResult can have None state."""
        from timmy.sovereignty.perception_cache import CacheResult

        result = CacheResult(confidence=0.5, state=None)
        assert result.confidence == 0.5
        assert result.state is None


class TestPerceptionCacheInit:
    """Tests for PerceptionCache initialization."""

    def test_init_creates_empty_cache_when_no_file(self, tmp_path):
        """Cache initializes empty when templates file doesn't exist."""
        from timmy.sovereignty.perception_cache import PerceptionCache

        templates_path = tmp_path / "nonexistent_templates.json"
        cache = PerceptionCache(templates_path=templates_path)

        assert cache.templates_path == templates_path
        assert cache.templates == []

    def test_init_loads_existing_templates(self, tmp_path):
        """Cache loads templates from existing JSON file."""
        from timmy.sovereignty.perception_cache import PerceptionCache

        templates_path = tmp_path / "templates.json"
        templates_data = [
            {"name": "template1", "threshold": 0.85},
            {"name": "template2", "threshold": 0.90},
        ]
        with open(templates_path, "w") as f:
            json.dump(templates_data, f)

        cache = PerceptionCache(templates_path=templates_path)

        assert len(cache.templates) == 2
        assert cache.templates[0].name == "template1"
        assert cache.templates[0].threshold == 0.85
        assert cache.templates[1].name == "template2"
        assert cache.templates[1].threshold == 0.90

    def test_init_with_string_path(self, tmp_path):
        """Cache accepts string path for templates."""
        from timmy.sovereignty.perception_cache import PerceptionCache

        templates_path = str(tmp_path / "templates.json")
        cache = PerceptionCache(templates_path=templates_path)

        assert str(cache.templates_path) == templates_path


class TestPerceptionCacheMatch:
    """Tests for PerceptionCache.match() template matching."""

    def test_match_no_templates_returns_low_confidence(self, tmp_path):
        """Matching with no templates returns low confidence and None state."""
        from timmy.sovereignty.perception_cache import PerceptionCache

        cache = PerceptionCache(templates_path=tmp_path / "templates.json")
        screenshot = np.array([[1, 2], [3, 4]])

        result = cache.match(screenshot)

        assert result.confidence == 0.0
        assert result.state is None

    @patch("timmy.sovereignty.perception_cache.cv2")
    def test_match_finds_best_template(self, mock_cv2, tmp_path):
        """Match returns the best matching template above threshold."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        # Setup mock cv2 behavior
        mock_cv2.matchTemplate.return_value = np.array([[0.5, 0.6], [0.7, 0.8]])
        mock_cv2.TM_CCOEFF_NORMED = "TM_CCOEFF_NORMED"
        mock_cv2.minMaxLoc.return_value = (None, 0.92, None, None)

        cache = PerceptionCache(templates_path=tmp_path / "templates.json")
        template = Template(name="best_match", image=np.array([[1, 2], [3, 4]]))
        cache.add([template])

        screenshot = np.array([[5, 6], [7, 8]])
        result = cache.match(screenshot)

        assert result.confidence == 0.92
        assert result.state == {"template_name": "best_match"}

    @patch("timmy.sovereignty.perception_cache.cv2")
    def test_match_respects_global_threshold(self, mock_cv2, tmp_path):
        """Match returns None state when confidence is below threshold."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        # Setup mock cv2 to return confidence below 0.85 threshold
        mock_cv2.matchTemplate.return_value = np.array([[0.1, 0.2], [0.3, 0.4]])
        mock_cv2.TM_CCOEFF_NORMED = "TM_CCOEFF_NORMED"
        mock_cv2.minMaxLoc.return_value = (None, 0.75, None, None)

        cache = PerceptionCache(templates_path=tmp_path / "templates.json")
        template = Template(name="low_match", image=np.array([[1, 2], [3, 4]]))
        cache.add([template])

        screenshot = np.array([[5, 6], [7, 8]])
        result = cache.match(screenshot)

        # Confidence is recorded but state is None (below threshold)
        assert result.confidence == 0.75
        assert result.state is None

    @patch("timmy.sovereignty.perception_cache.cv2")
    def test_match_selects_highest_confidence(self, mock_cv2, tmp_path):
        """Match selects template with highest confidence across all templates."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        mock_cv2.TM_CCOEFF_NORMED = "TM_CCOEFF_NORMED"

        # Each template will return a different confidence
        mock_cv2.minMaxLoc.side_effect = [
            (None, 0.70, None, None),  # template1
            (None, 0.95, None, None),  # template2 (best)
            (None, 0.80, None, None),  # template3
        ]

        cache = PerceptionCache(templates_path=tmp_path / "templates.json")
        templates = [
            Template(name="template1", image=np.array([[1, 2], [3, 4]])),
            Template(name="template2", image=np.array([[5, 6], [7, 8]])),
            Template(name="template3", image=np.array([[9, 10], [11, 12]])),
        ]
        cache.add(templates)

        screenshot = np.array([[13, 14], [15, 16]])
        result = cache.match(screenshot)

        assert result.confidence == 0.95
        assert result.state == {"template_name": "template2"}

    @patch("timmy.sovereignty.perception_cache.cv2")
    def test_match_exactly_at_threshold(self, mock_cv2, tmp_path):
        """Match returns state when confidence is exactly at threshold boundary."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        mock_cv2.matchTemplate.return_value = np.array([[0.1]])
        mock_cv2.TM_CCOEFF_NORMED = "TM_CCOEFF_NORMED"
        mock_cv2.minMaxLoc.return_value = (None, 0.85, None, None)  # Exactly at threshold

        cache = PerceptionCache(templates_path=tmp_path / "templates.json")
        template = Template(name="threshold_match", image=np.array([[1, 2], [3, 4]]))
        cache.add([template])

        screenshot = np.array([[5, 6], [7, 8]])
        result = cache.match(screenshot)

        # Implementation uses >= 0.85 (inclusive threshold)
        assert result.confidence == 0.85
        assert result.state is not None
        assert result.state["template_name"] == "threshold_match"

    @patch("timmy.sovereignty.perception_cache.cv2")
    def test_match_just_above_threshold(self, mock_cv2, tmp_path):
        """Match returns state when confidence is just above threshold."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        mock_cv2.matchTemplate.return_value = np.array([[0.1]])
        mock_cv2.TM_CCOEFF_NORMED = "TM_CCOEFF_NORMED"
        mock_cv2.minMaxLoc.return_value = (None, 0.851, None, None)  # Just above threshold

        cache = PerceptionCache(templates_path=tmp_path / "templates.json")
        template = Template(name="above_threshold", image=np.array([[1, 2], [3, 4]]))
        cache.add([template])

        screenshot = np.array([[5, 6], [7, 8]])
        result = cache.match(screenshot)

        assert result.confidence == 0.851
        assert result.state == {"template_name": "above_threshold"}


class TestPerceptionCacheAdd:
    """Tests for PerceptionCache.add() method."""

    def test_add_single_template(self, tmp_path):
        """Can add a single template to the cache."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        cache = PerceptionCache(templates_path=tmp_path / "templates.json")
        template = Template(name="new_template", image=np.array([[1, 2], [3, 4]]))

        cache.add([template])

        assert len(cache.templates) == 1
        assert cache.templates[0].name == "new_template"

    def test_add_multiple_templates(self, tmp_path):
        """Can add multiple templates at once."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        cache = PerceptionCache(templates_path=tmp_path / "templates.json")
        templates = [
            Template(name="template1", image=np.array([[1, 2], [3, 4]])),
            Template(name="template2", image=np.array([[5, 6], [7, 8]])),
        ]

        cache.add(templates)

        assert len(cache.templates) == 2
        assert cache.templates[0].name == "template1"
        assert cache.templates[1].name == "template2"

    def test_add_templates_accumulate(self, tmp_path):
        """Adding templates multiple times accumulates them."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        cache = PerceptionCache(templates_path=tmp_path / "templates.json")
        cache.add([Template(name="first", image=np.array([[1]]))])
        cache.add([Template(name="second", image=np.array([[2]]))])

        assert len(cache.templates) == 2


class TestPerceptionCachePersist:
    """Tests for PerceptionCache.persist() method."""

    def test_persist_creates_file(self, tmp_path):
        """Persist creates templates JSON file."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        templates_path = tmp_path / "subdir" / "templates.json"
        cache = PerceptionCache(templates_path=templates_path)
        cache.add([Template(name="persisted", image=np.array([[1, 2], [3, 4]]))])

        cache.persist()

        assert templates_path.exists()

    def test_persist_stores_template_names(self, tmp_path):
        """Persist stores template names and thresholds."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        templates_path = tmp_path / "templates.json"
        cache = PerceptionCache(templates_path=templates_path)
        cache.add(
            [
                Template(name="template1", image=np.array([[1]]), threshold=0.85),
                Template(name="template2", image=np.array([[2]]), threshold=0.90),
            ]
        )

        cache.persist()

        with open(templates_path) as f:
            data = json.load(f)

        assert len(data) == 2
        assert data[0]["name"] == "template1"
        assert data[0]["threshold"] == 0.85
        assert data[1]["name"] == "template2"
        assert data[1]["threshold"] == 0.90

    def test_persist_does_not_store_image_data(self, tmp_path):
        """Persist only stores metadata, not actual image arrays."""
        from timmy.sovereignty.perception_cache import PerceptionCache, Template

        templates_path = tmp_path / "templates.json"
        cache = PerceptionCache(templates_path=templates_path)
        cache.add([Template(name="no_image", image=np.array([[1, 2, 3], [4, 5, 6]]))])

        cache.persist()

        with open(templates_path) as f:
            data = json.load(f)

        assert "image" not in data[0]  # raw image array is NOT in JSON
        # image_path is stored for .npy file reference
        assert "name" in data[0]
        assert "threshold" in data[0]


class TestPerceptionCacheLoad:
    """Tests for PerceptionCache.load() method."""

    def test_load_from_existing_file(self, tmp_path):
        """Load restores templates from persisted file."""
        from timmy.sovereignty.perception_cache import PerceptionCache

        templates_path = tmp_path / "templates.json"

        # Create initial cache with templates and persist
        cache1 = PerceptionCache(templates_path=templates_path)
        from timmy.sovereignty.perception_cache import Template

        cache1.add([Template(name="loaded", image=np.array([[1]]), threshold=0.88)])
        cache1.persist()

        # Create new cache instance that loads from same file
        cache2 = PerceptionCache(templates_path=templates_path)

        assert len(cache2.templates) == 1
        assert cache2.templates[0].name == "loaded"
        assert cache2.templates[0].threshold == 0.88
        # Images are now persisted as .npy files and loaded back
        assert cache2.templates[0].image.size > 0

    def test_load_empty_file(self, tmp_path):
        """Load handles empty template list in file."""
        from timmy.sovereignty.perception_cache import PerceptionCache

        templates_path = tmp_path / "templates.json"
        with open(templates_path, "w") as f:
            json.dump([], f)

        cache = PerceptionCache(templates_path=templates_path)

        assert cache.templates == []


class TestCrystallizePerception:
    """Tests for crystallize_perception function."""

    def test_crystallize_returns_empty_list(self, tmp_path):
        """crystallize_perception currently returns empty list (placeholder)."""
        from timmy.sovereignty.perception_cache import crystallize_perception

        screenshot = np.array([[1, 2], [3, 4]])
        result = crystallize_perception(screenshot, {"some": "response"})

        assert result == []

    def test_crystallize_accepts_any_vlm_response(self, tmp_path):
        """crystallize_perception accepts any vlm_response format."""
        from timmy.sovereignty.perception_cache import crystallize_perception

        screenshot = np.array([[1, 2], [3, 4]])

        # Test with various response types
        assert crystallize_perception(screenshot, None) == []
        assert crystallize_perception(screenshot, {}) == []
        assert crystallize_perception(screenshot, {"items": []}) == []
        assert crystallize_perception(screenshot, "string response") == []
