"""Tests for async as3fifo_cache decorator."""

import asyncio

import pytest

from s3fifo import as3fifo_cache


class TestAS3FIFOCacheDecorator:
    @pytest.mark.asyncio
    async def test_basic_caching(self):
        call_count = 0

        @as3fifo_cache
        async def add(x, y):
            nonlocal call_count
            call_count += 1
            return x + y

        assert await add(1, 2) == 3
        assert await add(1, 2) == 3
        assert call_count == 1

    @pytest.mark.asyncio
    async def test_with_maxsize(self):
        call_count = 0

        @as3fifo_cache(maxsize=2)
        async def square(x):
            nonlocal call_count
            call_count += 1
            return x * x

        assert await square(2) == 4
        assert await square(3) == 9
        assert await square(2) == 4
        info = square.cache_info()
        assert info.hits >= 1

    @pytest.mark.asyncio
    async def test_cache_info(self):
        @as3fifo_cache(maxsize=10)
        async def f(x):
            return x

        await f(1)
        await f(2)
        await f(1)
        info = f.cache_info()
        assert info.hits == 1
        assert info.misses == 2
        assert info.maxsize == 10
        assert info.currsize == 2

    @pytest.mark.asyncio
    async def test_cache_clear(self):
        @as3fifo_cache(maxsize=10)
        async def f(x):
            return x

        await f(1)
        await f(2)
        f.cache_clear()
        info = f.cache_info()
        assert info.hits == 0
        assert info.misses == 0
        assert info.currsize == 0

    @pytest.mark.asyncio
    async def test_cache_parameters(self):
        @as3fifo_cache(
            maxsize=64,
            typed=True,
            small_size_ratio=0.2,
            ghost_size_ratio=0.5,
            move_to_main_threshold=3,
        )
        async def f(x):
            return x

        params = f.cache_parameters()
        assert params["maxsize"] == 64
        assert params["typed"] is True
        assert params["small_size_ratio"] == 0.2
        assert params["ghost_size_ratio"] == 0.5
        assert params["move_to_main_threshold"] == 3

    @pytest.mark.asyncio
    async def test_wrapped(self):
        async def original(x):
            return x

        cached = as3fifo_cache(original)
        assert cached.__wrapped__ is original

    @pytest.mark.asyncio
    async def test_exception_not_cached(self):
        call_count = 0

        @as3fifo_cache(maxsize=10)
        async def f(x):
            nonlocal call_count
            call_count += 1
            raise ValueError("boom")

        with pytest.raises(ValueError):
            await f(1)
        with pytest.raises(ValueError):
            await f(1)
        assert call_count == 2

    @pytest.mark.asyncio
    async def test_requires_coroutine(self):
        with pytest.raises(RuntimeError, match="Coroutine function is required"):

            @as3fifo_cache
            def not_async(x):
                return x

    @pytest.mark.asyncio
    async def test_cache_invalidate(self):
        call_count = 0

        @as3fifo_cache(maxsize=10)
        async def f(x):
            nonlocal call_count
            call_count += 1
            return x

        await f(1)
        assert call_count == 1
        assert f.cache_invalidate(1) is True
        await f(1)
        assert call_count == 2

    @pytest.mark.asyncio
    async def test_concurrent_calls_share_task(self):
        call_count = 0

        @as3fifo_cache(maxsize=10)
        async def f(x):
            nonlocal call_count
            call_count += 1
            await asyncio.sleep(0.01)
            return x

        results = await asyncio.gather(f(1), f(1), f(1))
        assert results == [1, 1, 1]
        assert call_count == 1

    @pytest.mark.asyncio
    async def test_cache_close_cancels_in_flight_tasks(self):
        started = asyncio.Event()
        gate = asyncio.Event()

        @as3fifo_cache(maxsize=10)
        async def f(x):
            started.set()
            await gate.wait()
            return x

        in_flight = asyncio.create_task(f(1))
        await started.wait()
        params = f.cache_parameters()
        assert params["tasks"] >= 1

        await f.cache_close(wait=False)
        with pytest.raises(asyncio.CancelledError):
            await in_flight

        with pytest.raises(RuntimeError, match="as3fifo_cache is closed"):
            await f(1)
