"""Tests for MemoryBackend."""

import asyncio
import pytest

from cachestack import MemoryBackend, MemoryConfig


@pytest.mark.asyncio
async def test_set_and_get(memory_cache):
    await memory_cache.set("key1", "value1")
    result = await memory_cache.get("key1")
    assert result == "value1"


@pytest.mark.asyncio
async def test_get_missing_key(memory_cache):
    result = await memory_cache.get("nonexistent")
    assert result is None


@pytest.mark.asyncio
async def test_delete(memory_cache):
    await memory_cache.set("key1", "value1")
    await memory_cache.delete("key1")
    result = await memory_cache.get("key1")
    assert result is None


@pytest.mark.asyncio
async def test_exists(memory_cache):
    await memory_cache.set("key1", "value1")
    assert await memory_cache.exists("key1") is True
    assert await memory_cache.exists("missing") is False


@pytest.mark.asyncio
async def test_clear(memory_cache):
    await memory_cache.set("a", 1)
    await memory_cache.set("b", 2)
    await memory_cache.clear()
    assert await memory_cache.get("a") is None
    assert await memory_cache.get("b") is None


@pytest.mark.asyncio
async def test_ttl_expiry():
    cache = MemoryBackend(MemoryConfig(policy="lru", maxsize=128))
    await cache.set("key", "value", ttl=1)
    result = await cache.get("key")
    assert result == "value"
    await asyncio.sleep(1.1)
    result = await cache.get("key")
    assert result is None


@pytest.mark.asyncio
async def test_get_many(memory_cache):
    await memory_cache.set("a", 1)
    await memory_cache.set("b", 2)
    result = await memory_cache.get_many(["a", "b", "c"])
    assert result["a"] == 1
    assert result["b"] == 2
    assert result["c"] is None


@pytest.mark.asyncio
async def test_set_many(memory_cache):
    await memory_cache.set_many([("x", 10, None), ("y", 20, None)])
    assert await memory_cache.get("x") == 10
    assert await memory_cache.get("y") == 20


@pytest.mark.asyncio
async def test_lru_eviction():
    cache = MemoryBackend(MemoryConfig(policy="lru", maxsize=3))
    await cache.set("a", 1)
    await cache.set("b", 2)
    await cache.set("c", 3)
    # Access 'a' to make 'b' the LRU
    await cache.get("a")
    await cache.get("c")
    # Add a 4th item — 'b' should be evicted
    await cache.set("d", 4)
    result_b = await cache.get("b")
    assert result_b is None


@pytest.mark.asyncio
async def test_stats(memory_cache):
    await memory_cache.set("k", "v")
    await memory_cache.get("k")
    await memory_cache.get("missing")
    stats = await memory_cache.stats()
    assert stats["backend"] == "memory"
    assert stats["hits"] >= 1
    assert stats["misses"] >= 1


@pytest.mark.asyncio
async def test_thread_safety():
    cache = MemoryBackend(MemoryConfig(maxsize=1000))

    async def writer(n):
        for i in range(50):
            await cache.set(f"key-{n}-{i}", i)

    async def reader(n):
        for i in range(50):
            await cache.get(f"key-{n}-{i}")

    await asyncio.gather(*[writer(i) for i in range(5)], *[reader(i) for i in range(5)])
    stats = await cache.stats()
    assert stats["errors"] == 0
