import json
from typing import Any

import redis.asyncio as aioredis

from src.infrastructure.config.settings import settings


class RedisCache:
    def __init__(self) -> None:
        self._client: aioredis.Redis | None = None

    async def connect(self) -> None:
        self._client = aioredis.from_url(
            settings.REDIS_URL,
            decode_responses=True,
            max_connections=20,
        )

    async def disconnect(self) -> None:
        if self._client:
            await self._client.close()

    @property
    def client(self) -> aioredis.Redis:
        if self._client is None:
            raise RuntimeError("Redis client not connected. Call connect() first.")
        return self._client

    async def get(self, key: str) -> Any | None:
        data = await self.client.get(key)
        if data is None:
            return None
        try:
            return json.loads(data)
        except (json.JSONDecodeError, TypeError):
            return data

    async def set(self, key: str, value: Any, ttl: int | None = None) -> None:
        ttl = ttl or settings.REDIS_CACHE_TTL
        if isinstance(value, (dict, list, tuple)):
            value = json.dumps(value)
        await self.client.set(key, value, ex=ttl)

    async def delete(self, key: str) -> None:
        await self.client.delete(key)

    async def exists(self, key: str) -> bool:
        return bool(await self.client.exists(key))

    async def get_with_lock(self, key: str, lock_timeout: int = 10) -> Any | None:
        async with self.client.lock(f"{key}:lock", timeout=lock_timeout):
            return await self.get(key)

    async def set_with_lock(self, key: str, value: Any, ttl: int | None = None, lock_timeout: int = 10) -> None:
        async with self.client.lock(f"{key}:lock", timeout=lock_timeout):
            await self.set(key, value, ttl)

    async def invalidate_pattern(self, pattern: str) -> None:
        keys = []
        async for key in self.client.scan_iter(match=pattern):
            keys.append(key)
        if keys:
            await self.client.delete(*keys)

    async def health_check(self) -> bool:
        try:
            return await self.client.ping()
        except Exception:
            return False


redis_cache = RedisCache()
