"""Unit tests: TTLCache — TTL expiration, LRU eviction, disabled mode."""

from __future__ import annotations

import time

import pytest

from seaart._cache import TTLCache


class TestTTLCacheDisabled:
    def test_disabled_when_ttl_zero(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=0)
        assert not c.enabled

    def test_get_returns_default(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=0)
        c.set("a", 1)
        assert c.get("a") is None
        assert c.get("a", 42) == 42

    def test_contains_always_false(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=0)
        c.set("a", 1)
        assert "a" not in c

    def test_len_always_zero(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=0)
        c.set("a", 1)
        assert len(c) == 0

    def test_pop_returns_default(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=0)
        assert c.pop("x", 99) == 99


class TestTTLCacheBasic:
    def test_set_and_get(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60)
        c.set("a", 1)
        assert c.get("a") == 1

    def test_get_missing_key(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60)
        assert c.get("missing") is None
        assert c.get("missing", 42) == 42

    def test_contains(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60)
        c.set("a", 1)
        assert "a" in c
        assert "b" not in c

    def test_pop(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60)
        c.set("a", 1)
        assert c.pop("a") == 1
        assert c.get("a") is None

    def test_delete(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60)
        c.set("a", 1)
        c.delete("a")
        assert c.get("a") is None

    def test_clear(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60)
        c.set("a", 1)
        c.set("b", 2)
        c.clear()
        assert len(c) == 0

    def test_overwrite(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60)
        c.set("a", 1)
        c.set("a", 2)
        assert c.get("a") == 2

    def test_no_ttl_lives_forever(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=None)
        assert c.enabled
        c.set("a", 1)
        assert c.get("a") == 1

    def test_iter_returns_keys(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60)
        c.set("a", 1)
        c.set("b", 2)
        assert set(c) == {"a", "b"}


class TestTTLCacheExpiration:
    def test_expired_entry_returns_default(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=0.1)
        c.set("a", 1)
        assert c.get("a") == 1
        time.sleep(0.15)
        assert c.get("a") is None

    def test_expired_entry_not_in_contains(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=0.1)
        c.set("a", 1)
        time.sleep(0.15)
        assert "a" not in c

    def test_expired_pop_returns_default(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=0.1)
        c.set("a", 1)
        time.sleep(0.15)
        assert c.pop("a", 99) == 99


class TestTTLCacheLRU:
    def test_evicts_oldest_when_full(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60, maxsize=3)
        c.set("a", 1)
        c.set("b", 2)
        c.set("c", 3)
        c.set("d", 4)  # evicts "a"
        assert c.get("a") is None
        assert c.get("d") == 4

    def test_access_refreshes_lru(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60, maxsize=3)
        c.set("a", 1)
        c.set("b", 2)
        c.set("c", 3)
        c.get("a")  # refresh "a"
        c.set("d", 4)  # evicts "b" (oldest untouched)
        assert c.get("a") == 1
        assert c.get("b") is None

    def test_unbounded(self) -> None:
        c: TTLCache[int, int] = TTLCache(ttl_seconds=60, maxsize=None)
        for i in range(1000):
            c.set(i, i)
        assert len(c) == 1000


class TestTTLCacheValidation:
    def test_negative_ttl_raises(self) -> None:
        with pytest.raises(ValueError):
            TTLCache(ttl_seconds=-1)

    def test_zero_maxsize_raises(self) -> None:
        with pytest.raises(ValueError):
            TTLCache(maxsize=0)

    def test_zero_prune_every_raises(self) -> None:
        with pytest.raises(ValueError):
            TTLCache(prune_every=0)


class TestTTLCacheStats:
    def test_stats(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60, maxsize=100)
        c.set("a", 1)
        c.set("b", 2)
        s = c.stats()
        assert s["size"] == 2
        assert s["maxsize"] == 100
        assert s["writes"] == 2

    def test_repr(self) -> None:
        c: TTLCache[str, int] = TTLCache(ttl_seconds=60, maxsize=100)
        r = repr(c)
        assert "TTLCache" in r
        assert "60" in r
