"""
Redis cache backend using redis.asyncio.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Dict, Optional

from cachestack.base import BaseCache, BackendUnavailableError, CacheError
from cachestack.serializers import BaseSerializer, PickleSerializer


@dataclass
class RedisConfig:
    """Configuration for the Redis backend.

    Attributes:
        dsn: Redis connection string (e.g. 'redis://localhost:6379/0').
        serializer: Serializer instance to use for values.
        ttl: Default time-to-live in seconds.
        namespace: Key prefix to avoid collisions.
        silent: If True, suppress errors and return None instead of raising.
    """
    dsn: str = "redis://localhost:6379/0"
    serializer: BaseSerializer = field(default_factory=PickleSerializer)
    ttl: Optional[int] = None
    namespace: str = "cachestack"
    silent: bool = False


class RedisBackend(BaseCache):
    """
    Async Redis cache backend.

    Args:
        config: RedisConfig instance.
    """

    def __init__(self, config: Optional[RedisConfig] = None) -> None:
        self._config = config or RedisConfig()
        self._client: Any = None
        self._hits = 0
        self._misses = 0
        self._errors = 0

    def _prefixed(self, key: str) -> str:
        """Apply namespace prefix to key."""
        return f"{self._config.namespace}:{key}"

    async def _get_client(self) -> Any:
        """Lazily initialize the Redis client."""
        if self._client is None:
            try:
                import redis.asyncio as aioredis
                self._client = aioredis.from_url(
                    self._config.dsn,
                    decode_responses=False,
                )
            except ImportError as e:
                raise ImportError(
                    "redis is required for RedisBackend. "
                    "Install it with: pip install cachestack[redis]"
                ) from e
            except Exception as e:
                raise BackendUnavailableError(f"Failed to connect to Redis: {e}") from e
        return self._client

    async def get(self, key: str) -> Optional[Any]:
        """Retrieve a value by key."""
        try:
            client = await self._get_client()
            raw = await client.get(self._prefixed(key))
            if raw is None:
                self._misses += 1
                return None
            self._hits += 1
            return self._config.serializer.loads(raw)
        except (BackendUnavailableError, ImportError):
            raise
        except Exception as e:
            self._errors += 1
            if self._config.silent:
                return None
            raise CacheError(f"RedisBackend get failed: {e}") from e

    async def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
        """Store a value by key."""
        try:
            client = await self._get_client()
            raw = self._config.serializer.dumps(value)
            effective_ttl = ttl or self._config.ttl
            if effective_ttl:
                await client.setex(self._prefixed(key), effective_ttl, raw)
            else:
                await client.set(self._prefixed(key), raw)
        except (BackendUnavailableError, ImportError):
            raise
        except Exception as e:
            self._errors += 1
            if self._config.silent:
                return
            raise CacheError(f"RedisBackend set failed: {e}") from e

    async def delete(self, key: str) -> None:
        """Delete a key."""
        try:
            client = await self._get_client()
            await client.delete(self._prefixed(key))
        except (BackendUnavailableError, ImportError):
            raise
        except Exception as e:
            self._errors += 1
            if self._config.silent:
                return
            raise CacheError(f"RedisBackend delete failed: {e}") from e

    async def exists(self, key: str) -> bool:
        """Check if a key exists."""
        try:
            client = await self._get_client()
            return bool(await client.exists(self._prefixed(key)))
        except Exception as e:
            self._errors += 1
            if self._config.silent:
                return False
            raise CacheError(f"RedisBackend exists failed: {e}") from e

    async def clear(self) -> None:
        """Clear all keys under this namespace."""
        try:
            client = await self._get_client()
            pattern = f"{self._config.namespace}:*"
            keys = await client.keys(pattern)
            if keys:
                await client.delete(*keys)
        except (BackendUnavailableError, ImportError):
            raise
        except Exception as e:
            self._errors += 1
            if self._config.silent:
                return
            raise CacheError(f"RedisBackend clear failed: {e}") from e

    async def close(self) -> None:
        """Close the Redis connection."""
        if self._client:
            await self._client.aclose()
            self._client = None

    async def stats(self) -> Dict[str, Any]:
        """Return cache statistics."""
        return {
            "backend": "redis",
            "dsn": self._config.dsn,
            "namespace": self._config.namespace,
            "hits": self._hits,
            "misses": self._misses,
            "errors": self._errors,
        }
