"""Unit tests for app.cache LRU cache wrapper."""

from datetime import date

from app.cache import LRUCache


def _key(ticker: str, indicator: str = "RSI") -> tuple:
    return (ticker, date(2024, 1, 1), date(2024, 6, 1), indicator)


class TestLRUCacheBasics:
    def test_get_miss_returns_none(self):
        cache = LRUCache()
        assert cache.get(_key("AAPL")) is None

    def test_set_and_get_round_trip(self):
        cache = LRUCache()
        cache.set(_key("AAPL"), {"score": 72})
        assert cache.get(_key("AAPL")) == {"score": 72}

    def test_set_overwrites_existing(self):
        cache = LRUCache()
        cache.set(_key("AAPL"), 1)
        cache.set(_key("AAPL"), 2)
        assert cache.get(_key("AAPL")) == 2
        assert cache.size == 1

    def test_size_and_clear(self):
        cache = LRUCache(max_size=5)
        for i in range(3):
            cache.set((f"T{i}", None, None, "RSI"), i)
        assert cache.size == 3
        cache.clear()
        assert cache.size == 0


class TestLRUEviction:
    def test_evicts_lru_when_full(self):
        cache = LRUCache(max_size=3)
        cache.set(_key("A"), 1)
        cache.set(_key("B"), 2)
        cache.set(_key("C"), 3)
        # A is LRU — inserting D should evict A
        cache.set(_key("D"), 4)
        assert cache.get(_key("A")) is None
        assert cache.size == 3

    def test_access_promotes_entry(self):
        cache = LRUCache(max_size=3)
        cache.set(_key("A"), 1)
        cache.set(_key("B"), 2)
        cache.set(_key("C"), 3)
        # Access A so B becomes LRU
        cache.get(_key("A"))
        cache.set(_key("D"), 4)
        assert cache.get(_key("B")) is None
        assert cache.get(_key("A")) == 1

    def test_update_promotes_entry(self):
        cache = LRUCache(max_size=3)
        cache.set(_key("A"), 1)
        cache.set(_key("B"), 2)
        cache.set(_key("C"), 3)
        # Update A so B becomes LRU
        cache.set(_key("A"), 10)
        cache.set(_key("D"), 4)
        assert cache.get(_key("B")) is None
        assert cache.get(_key("A")) == 10


class TestTickerInvalidation:
    def test_invalidate_removes_all_entries_for_ticker(self):
        cache = LRUCache()
        cache.set(("AAPL", date(2024, 1, 1), date(2024, 6, 1), "RSI"), 1)
        cache.set(("AAPL", date(2024, 1, 1), date(2024, 6, 1), "ADX"), 2)
        cache.set(("MSFT", date(2024, 1, 1), date(2024, 6, 1), "RSI"), 3)
        removed = cache.invalidate_ticker("AAPL")
        assert removed == 2
        assert cache.get(("AAPL", date(2024, 1, 1), date(2024, 6, 1), "RSI")) is None
        assert cache.get(("AAPL", date(2024, 1, 1), date(2024, 6, 1), "ADX")) is None
        assert cache.get(("MSFT", date(2024, 1, 1), date(2024, 6, 1), "RSI")) == 3

    def test_invalidate_nonexistent_ticker_returns_zero(self):
        cache = LRUCache()
        cache.set(_key("AAPL"), 1)
        assert cache.invalidate_ticker("GOOG") == 0
        assert cache.size == 1

    def test_invalidate_on_empty_cache(self):
        cache = LRUCache()
        assert cache.invalidate_ticker("AAPL") == 0


class TestMaxSizeProperty:
    def test_default_max_size(self):
        cache = LRUCache()
        assert cache.max_size == 1000

    def test_custom_max_size(self):
        cache = LRUCache(max_size=50)
        assert cache.max_size == 50
