"""Integration tests for Redis adapter using testcontainers."""

import asyncio

import pytest

# Skip all tests if redis or testcontainers are not installed
pytest.importorskip("redis")
pytest.importorskip("testcontainers")

import redis.asyncio
from testcontainers.redis import RedisContainer

from t87s import CacheEntry, Tag
from t87s.adapters.redis import AsyncRedisAdapter


@pytest.fixture(scope="module")
def redis_container():
    """Start a Redis container for the test module."""
    with RedisContainer() as container:
        yield container


@pytest.fixture
def async_redis_client(redis_container):
    """Create an async Redis client."""
    client = redis.asyncio.Redis(
        host=redis_container.get_container_host_ip(),
        port=redis_container.get_exposed_port(6379),
        decode_responses=False,
    )
    yield client
    # Cleanup handled in test


@pytest.fixture
def async_redis_adapter(async_redis_client) -> AsyncRedisAdapter:
    """Create an AsyncRedisAdapter with a test prefix."""
    return AsyncRedisAdapter(async_redis_client, prefix="test")


class TestAsyncRedisAdapter:
    """Integration tests for async AsyncRedisAdapter."""

    @pytest.mark.asyncio
    async def test_get_nonexistent_returns_none(
        self, async_redis_adapter: AsyncRedisAdapter
    ) -> None:
        """Test that getting a nonexistent key returns None."""
        assert await async_redis_adapter.get("nonexistent") is None

    @pytest.mark.asyncio
    async def test_set_and_get(self, async_redis_adapter: AsyncRedisAdapter) -> None:
        """Test setting and getting a value."""
        entry: CacheEntry[object] = CacheEntry(
            value={"id": "456", "name": "Async Test"},
            tags=[Tag(("post", "456"))],
            created_at=2000,
            expires_at=9999999999999,
            grace_until=None,
        )
        await async_redis_adapter.set("async_key1", entry)
        result = await async_redis_adapter.get("async_key1")
        assert result is not None
        assert result.value == {"id": "456", "name": "Async Test"}
        assert result.tags == [("post", "456")]

    @pytest.mark.asyncio
    async def test_delete(self, async_redis_adapter: AsyncRedisAdapter) -> None:
        """Test deleting a value."""
        entry: CacheEntry[object] = CacheEntry(
            value="async_test",
            tags=[],
            created_at=1000,
            expires_at=9999999999999,
            grace_until=None,
        )
        await async_redis_adapter.set("async_key1", entry)
        await async_redis_adapter.delete("async_key1")
        assert await async_redis_adapter.get("async_key1") is None

    @pytest.mark.asyncio
    async def test_tag_invalidation_time(
        self, async_redis_adapter: AsyncRedisAdapter
    ) -> None:
        """Test setting and getting tag invalidation time."""
        tag = Tag(("post", "456"))
        assert await async_redis_adapter.get_tag_invalidation_time(tag) is None

        await async_redis_adapter.set_tag_invalidation_time(tag, 2000)
        assert await async_redis_adapter.get_tag_invalidation_time(tag) == 2000

    @pytest.mark.asyncio
    async def test_clear(self, async_redis_adapter: AsyncRedisAdapter) -> None:
        """Test clearing all cached entries."""
        entry: CacheEntry[object] = CacheEntry(
            value="test",
            tags=[],
            created_at=1000,
            expires_at=9999999999999,
            grace_until=None,
        )
        await async_redis_adapter.set("async_key1", entry)
        await async_redis_adapter.set("async_key2", entry)

        await async_redis_adapter.clear()

        assert await async_redis_adapter.get("async_key1") is None
        assert await async_redis_adapter.get("async_key2") is None

    @pytest.mark.asyncio
    async def test_ttl_expiration(self, async_redis_adapter: AsyncRedisAdapter) -> None:
        """Test that entries expire based on TTL."""
        import time

        entry: CacheEntry[object] = CacheEntry(
            value="async_expiring",
            tags=[],
            created_at=1000,
            expires_at=int(time.time() * 1000) + 100,  # 100ms from now
            grace_until=None,
        )
        await async_redis_adapter.set("async_expiring_key", entry)

        # Should exist immediately
        assert await async_redis_adapter.get("async_expiring_key") is not None

        # Wait for expiration
        await asyncio.sleep(0.2)

        # Should be gone
        assert await async_redis_adapter.get("async_expiring_key") is None
