"""Redis cache backend for pretok."""

from __future__ import annotations

import json
import logging
from typing import Any

from pretok.pipeline.cache import Cache

logger = logging.getLogger(__name__)


class RedisCache(Cache):
    """Redis-based cache backend.

    Requires the ``redis`` package::

        pip install pretok[redis]

    Example:
        >>> cache = RedisCache(url="redis://localhost:6379")
        >>> cache.set("key", "value")
        >>> cache.get("key")
        'value'
    """

    def __init__(
        self,
        url: str = "redis://localhost:6379",
        prefix: str = "pretok",
        ttl: int = 86400,
    ) -> None:
        """Initialize Redis cache.

        Args:
            url: Redis connection URL
            prefix: Key prefix for cache entries
            ttl: Time-to-live in seconds (0 = no expiry)

        Raises:
            ImportError: If redis package is not installed
        """
        try:
            import redis
        except ImportError as e:
            msg = "redis package is required for RedisCache. Install it with: pip install redis"
            raise ImportError(msg) from e

        self._client = redis.from_url(url, decode_responses=True)
        self._prefix = prefix
        self._ttl = ttl
        self._hits = 0
        self._misses = 0

        logger.info("Initialized RedisCache: url=%s, prefix=%s, ttl=%d", url, prefix, ttl)

    def _make_key(self, key: str) -> str:
        """Create prefixed Redis key."""
        return f"{self._prefix}:{key}"

    def get(self, key: str) -> Any | None:
        """Get value from Redis cache.

        Args:
            key: Cache key

        Returns:
            Cached value or None if not found
        """
        redis_key = self._make_key(key)
        raw = self._client.get(redis_key)

        if raw is None:
            self._misses += 1
            return None

        self._hits += 1
        try:
            return json.loads(raw)
        except (json.JSONDecodeError, TypeError):
            return raw

    def set(self, key: str, value: Any) -> None:
        """Set value in Redis cache.

        Args:
            key: Cache key
            value: Value to cache
        """
        redis_key = self._make_key(key)
        serialized = json.dumps(value) if not isinstance(value, str) else value

        if self._ttl > 0:
            self._client.setex(redis_key, self._ttl, serialized)
        else:
            self._client.set(redis_key, serialized)

    def delete(self, key: str) -> bool:
        """Delete value from Redis cache.

        Args:
            key: Cache key

        Returns:
            True if key was deleted
        """
        redis_key = self._make_key(key)
        return bool(self._client.delete(redis_key))

    def clear(self) -> None:
        """Clear all entries with this prefix from Redis."""
        pattern = self._make_key("*")
        cursor = 0
        while True:
            cursor, keys = self._client.scan(cursor=cursor, match=pattern, count=100)
            if keys:
                self._client.delete(*keys)
            if cursor == 0:
                break
        self._hits = 0
        self._misses = 0

    def size(self) -> int:
        """Return approximate number of entries with this prefix."""
        pattern = self._make_key("*")
        count = 0
        cursor = 0
        while True:
            cursor, keys = self._client.scan(cursor=cursor, match=pattern, count=100)
            count += len(keys)
            if cursor == 0:
                break
        return count

    @property
    def hit_rate(self) -> float:
        """Return cache hit rate."""
        total = self._hits + self._misses
        if total == 0:
            return 0.0
        return self._hits / total

    @property
    def stats(self) -> dict[str, Any]:
        """Return cache statistics."""
        return {
            "backend": "redis",
            "prefix": self._prefix,
            "hits": self._hits,
            "misses": self._misses,
            "hit_rate": self.hit_rate,
        }
