"""Tests for @cached and @invalidate decorators."""

import pytest

from cachestack import MemoryBackend, MemoryConfig, cached, invalidate


@pytest.mark.asyncio
async def test_cached_async_function():
    cache = MemoryBackend()
    call_count = 0

    @cached(cache=cache, ttl=60)
    async def fetch(x: int):
        nonlocal call_count
        call_count += 1
        return x * 2

    result1 = await fetch(5)
    result2 = await fetch(5)
    assert result1 == 10
    assert result2 == 10
    assert call_count == 1  # Only called once


@pytest.mark.asyncio
async def test_cached_custom_key_builder():
    cache = MemoryBackend()
    call_count = 0

    @cached(
        cache=cache,
        ttl=60,
        key_builder=lambda fn, args, kw: f"user:{args[0]}",
    )
    async def get_user(user_id: int):
        nonlocal call_count
        call_count += 1
        return {"id": user_id, "name": "Sarthak"}

    await get_user(42)
    await get_user(42)
    assert call_count == 1
    assert await cache.exists("user:42")


@pytest.mark.asyncio
async def test_cached_condition():
    cache = MemoryBackend()
    call_count = 0

    @cached(
        cache=cache,
        ttl=60,
        condition=lambda v: v is not None,
    )
    async def maybe_fetch(flag: bool):
        nonlocal call_count
        call_count += 1
        return "data" if flag else None

    # None result should not be cached
    r1 = await maybe_fetch(False)
    r2 = await maybe_fetch(False)
    assert r1 is None
    assert r2 is None
    assert call_count == 2  # Called twice because None not cached

    # Non-None result should be cached
    r3 = await maybe_fetch(True)
    r4 = await maybe_fetch(True)
    assert r3 == "data"
    assert r4 == "data"
    assert call_count == 3  # Only one more call


@pytest.mark.asyncio
async def test_invalidate_decorator():
    cache = MemoryBackend()
    key_fn = lambda fn, args, kw: f"user:{args[0]}"

    @cached(cache=cache, ttl=60, key_builder=key_fn)
    async def get_user(user_id: int):
        return {"id": user_id}

    @invalidate(cache=cache, key_builder=key_fn)
    async def update_user(user_id: int, data: dict):
        return True

    await get_user(1)
    assert await cache.exists("user:1")
    await update_user(1, {"name": "updated"})
    assert not await cache.exists("user:1")


def test_cached_sync_function():
    cache = MemoryBackend()
    call_count = 0

    @cached(cache=cache, ttl=60)
    def compute(x: int):
        nonlocal call_count
        call_count += 1
        return x ** 2

    r1 = compute(4)
    r2 = compute(4)
    assert r1 == 16
    assert r2 == 16
    assert call_count == 1
