"""
Tests for cortex/hippo/cache.py — LRU cache with TTL.

Issue: #1190
"""

from __future__ import annotations

import time
from dataclasses import dataclass
from unittest.mock import patch

import pytest

from cortex.hippo.cache import SummaryCache


@dataclass
class FakeSummary:
    summary_id: str
    content: str = "test"
    token_count: int = 10


class TestSummaryCacheHit:
    def test_cache_hit(self):
        cache = SummaryCache(max_size=10, ttl_seconds=60)
        rec = FakeSummary(summary_id="sum_aaa")
        cache.put("sum_aaa", rec)
        assert cache.get("sum_aaa") is rec
        assert cache.stats["hits"] == 1
        assert cache.stats["misses"] == 0

    def test_cache_miss(self):
        cache = SummaryCache(max_size=10, ttl_seconds=60)
        assert cache.get("sum_nonexistent") is None
        assert cache.stats["misses"] == 1

    def test_cache_ttl_expiry(self):
        cache = SummaryCache(max_size=10, ttl_seconds=1.0)
        rec = FakeSummary(summary_id="sum_bbb")
        cache.put("sum_bbb", rec)

        # Patch time to simulate expiry
        original_entry = cache._cache["sum_bbb"]
        original_entry.cached_at = time.monotonic() - 2.0

        assert cache.get("sum_bbb") is None
        assert cache.stats["misses"] == 1

    def test_cache_lru_eviction(self):
        cache = SummaryCache(max_size=3, ttl_seconds=60)
        for i in range(4):
            cache.put(f"sum_{i}", FakeSummary(summary_id=f"sum_{i}"))

        # sum_0 should be evicted (LRU)
        assert cache.get("sum_0") is None
        # sum_1, sum_2, sum_3 should still be there
        assert cache.get("sum_1") is not None
        assert cache.get("sum_2") is not None
        assert cache.get("sum_3") is not None
        assert cache.size == 3

    def test_cache_invalidation(self):
        cache = SummaryCache(max_size=10, ttl_seconds=60)
        rec = FakeSummary(summary_id="sum_ccc")
        cache.put("sum_ccc", rec)
        assert cache.get("sum_ccc") is rec

        cache.invalidate("sum_ccc")
        assert cache.get("sum_ccc") is None

    def test_cache_clear(self):
        cache = SummaryCache(max_size=10, ttl_seconds=60)
        for i in range(5):
            cache.put(f"sum_{i}", FakeSummary(summary_id=f"sum_{i}"))

        assert cache.size == 5
        cache.clear()
        assert cache.size == 0
        assert cache.stats["hits"] == 0
        assert cache.stats["misses"] == 0

    def test_cache_hit_rate(self):
        cache = SummaryCache(max_size=10, ttl_seconds=60)
        cache.put("sum_x", FakeSummary(summary_id="sum_x"))

        # 2 hits + 1 miss = 2/3 hit rate
        cache.get("sum_x")
        cache.get("sum_x")
        cache.get("sum_missing")

        assert cache.hit_rate == pytest.approx(2 / 3)
        assert cache.stats["hits"] == 2
        assert cache.stats["misses"] == 1

    def test_get_batch(self):
        cache = SummaryCache(max_size=10, ttl_seconds=60)
        rec1 = FakeSummary(summary_id="sum_1")
        rec2 = FakeSummary(summary_id="sum_2")
        cache.put("sum_1", rec1)
        cache.put("sum_2", rec2)

        found, missing = cache.get_batch(["sum_1", "sum_2", "sum_3"])
        assert len(found) == 2
        assert missing == ["sum_3"]

    def test_put_batch(self):
        cache = SummaryCache(max_size=10, ttl_seconds=60)
        records = [FakeSummary(summary_id=f"sum_{i}") for i in range(3)]
        cache.put_batch(records)

        assert cache.size == 3
        for i in range(3):
            assert cache.get(f"sum_{i}") is not None

    def test_update_existing_entry(self):
        cache = SummaryCache(max_size=10, ttl_seconds=60)
        rec1 = FakeSummary(summary_id="sum_a", content="old")
        rec2 = FakeSummary(summary_id="sum_a", content="new")

        cache.put("sum_a", rec1)
        cache.put("sum_a", rec2)

        result = cache.get("sum_a")
        assert result.content == "new"
        assert cache.size == 1
