"""Tests for TieredCache."""

import asyncio
import pytest

from cachestack import MemoryBackend, MemoryConfig, TieredCache, WriteStrategy


@pytest.mark.asyncio
async def test_write_through(tiered_cache):
    cache, l1, l2 = tiered_cache
    await cache.set("key", "value")
    assert await l1.get("key") == "value"
    assert await l2.get("key") == "value"


@pytest.mark.asyncio
async def test_write_back():
    l1 = MemoryBackend(MemoryConfig(maxsize=64))
    l2 = MemoryBackend(MemoryConfig(maxsize=256))
    cache = TieredCache([l1, l2], write_strategy=WriteStrategy.WRITE_BACK)
    await cache.set("key", "value")
    assert await l1.get("key") == "value"
    assert await l2.get("key") is None


@pytest.mark.asyncio
async def test_read_through_and_backfill():
    l1 = MemoryBackend(MemoryConfig(maxsize=64))
    l2 = MemoryBackend(MemoryConfig(maxsize=256))
    cache = TieredCache([l1, l2])
    # Populate only L2
    await l2.set("key", "from_l2")
    # L1 miss should trigger backfill from L2
    result = await cache.get("key")
    assert result == "from_l2"
    # Now L1 should have it
    assert await l1.get("key") == "from_l2"


@pytest.mark.asyncio
async def test_miss_returns_none(tiered_cache):
    cache, _, _ = tiered_cache
    result = await cache.get("nonexistent")
    assert result is None


@pytest.mark.asyncio
async def test_delete_propagates(tiered_cache):
    cache, l1, l2 = tiered_cache
    await cache.set("key", "value")
    await cache.delete("key")
    assert await l1.get("key") is None
    assert await l2.get("key") is None


@pytest.mark.asyncio
async def test_clear_propagates(tiered_cache):
    cache, l1, l2 = tiered_cache
    await cache.set("a", 1)
    await cache.set("b", 2)
    await cache.clear()
    assert await cache.get("a") is None
    assert await cache.get("b") is None


@pytest.mark.asyncio
async def test_stampede_protection():
    """Multiple concurrent coroutines hitting a cold key should not cause races."""
    call_count = 0
    l1 = MemoryBackend(MemoryConfig(maxsize=64))
    l2 = MemoryBackend(MemoryConfig(maxsize=256))
    cache = TieredCache([l1, l2])

    await l2.set("hot_key", "expensive_value")

    async def fetch():
        return await cache.get("hot_key")

    results = await asyncio.gather(*[fetch() for _ in range(20)])
    assert all(r == "expensive_value" for r in results)


@pytest.mark.asyncio
async def test_stats(tiered_cache):
    cache, _, _ = tiered_cache
    await cache.set("k", "v")
    await cache.get("k")
    await cache.get("missing")
    stats = await cache.stats()
    assert stats["backend"] == "tiered"
    assert stats["layers"] == 2
    assert len(stats["layer_stats"]) == 2
    assert stats["hits"] >= 1
    assert stats["misses"] >= 1
