"""Dogpile cache configuration for SQLCipher-encrypted databases.

This module provides a simple in-memory caching system optimized for per-user
encrypted databases with SQLAlchemy and SQLCipher.
"""

import threading
from typing import Any, Dict, Optional, Union

from dogpile.cache import CacheRegion, make_region
from dogpile.cache.api import NO_VALUE
from loguru import logger

# Thread-local storage for cache regions (matches session pattern)
thread_local = threading.local()

# Cache configuration constants
DEFAULT_CACHE_TTL = 3600  # 1 hour


def get_namespaced_key(user_id: str, namespace: str, key: str) -> str:
    """Create namespaced cache key.

    Args:
        user_id: User identifier
        namespace: Cache namespace (e.g., 'settings', 'api_keys')
        key: Specific cache key

    Returns:
        Namespaced cache key
    """
    # Simple string concatenation for in-memory cache keys
    return f"ldr:{user_id}:{namespace}:{key}"


def create_null_cache_region() -> CacheRegion:
    """Create a null cache region for testing/development."""
    region = make_region()
    region.configure("dogpile.cache.null")
    return region


class SingleTierCache:
    """Simple single-tier in-memory cache with configurable TTL."""

    def __init__(
        self,
        cache_region: Optional[CacheRegion] = None,
        enable_metrics: bool = True,
        default_ttl: int = DEFAULT_CACHE_TTL,
    ):
        """Initialize single-tier cache.

        Args:
            cache_region: In-memory cache region (defaults to 1 hour TTL)
            enable_metrics: Whether to track cache metrics
            default_ttl: Default TTL in seconds if no cache_region provided
        """
        if cache_region:
            self.cache = cache_region
        else:
            # Create a single cache with moderate TTL (1 hour by default)
            self.cache = make_region()
            self.cache.configure(
                "dogpile.cache.memory",
                expiration_time=default_ttl,
                arguments={
                    "cache_dict": {},
                    "max_size": 10000,  # Reasonable size for in-memory cache
                },
            )

        self.enable_metrics = enable_metrics
        # Simplified metrics
        self.metrics = {"hits": 0, "misses": 0, "errors": 0}

    def get(self, user_id: str, namespace: str, key: str) -> Any:
        """Get value from cache.

        Args:
            user_id: User identifier
            namespace: Cache namespace
            key: Cache key

        Returns:
            Cached value or NO_VALUE if not found
        """
        cache_key = get_namespaced_key(user_id, namespace, key)

        try:
            value = self.cache.get(cache_key)
            if value is not NO_VALUE:
                if self.enable_metrics:
                    self.metrics["hits"] += 1
                return value
        except Exception as e:
            logger.warning(f"Cache error: {e}")
            if self.enable_metrics:
                self.metrics["errors"] += 1

        if self.enable_metrics:
            self.metrics["misses"] += 1

        return NO_VALUE

    def set(
        self,
        user_id: str,
        namespace: str,
        key: str,
        value: Any,
        expiration_time: Optional[int] = None,
    ) -> None:
        """Set value in cache.

        Args:
            user_id: User identifier
            namespace: Cache namespace
            key: Cache key
            value: Value to cache
            expiration_time: Optional TTL override (currently ignored - uses region TTL)
        """
        cache_key = get_namespaced_key(user_id, namespace, key)

        try:
            self.cache.set(cache_key, value)
        except Exception as e:
            logger.warning(f"Failed to set cache: {e}")
            if self.enable_metrics:
                self.metrics["errors"] += 1

    def invalidate(
        self,
        user_id: str,
        namespace: Optional[str] = None,
        pattern: Optional[str] = None,
    ) -> None:
        """Invalidate cache entries for a user.

        Args:
            user_id: User identifier
            namespace: Optional namespace to invalidate (None = all)
            pattern: Optional pattern within namespace (e.g., "llm.*")
        """
        # Since we're using memory caches, we need to iterate through keys
        # This is less efficient than Redis SCAN but works for memory caches

        if pattern and namespace:
            # Pattern-based invalidation within namespace
            cache_pattern = f"ldr:{user_id}:{namespace}:"
            logger.info(f"Invalidating cache pattern: {cache_pattern}{pattern}")

            # For memory caches, we need to check each key
            # This is a limitation of dogpile's memory backend
            self._invalidate_memory_pattern(cache_pattern, pattern)

        elif namespace:
            # Invalidate entire namespace
            cache_pattern = f"ldr:{user_id}:{namespace}:"
            logger.info(f"Invalidating cache namespace: {cache_pattern}*")

            # For memory caches, clear matching keys
            self._invalidate_memory_pattern(cache_pattern, "*")

        else:
            # Invalidate all user data
            cache_pattern = f"ldr:{user_id}:"
            logger.info(f"Invalidating all cache for user: {cache_pattern}*")

            # For memory caches, clear matching keys
            self._invalidate_memory_pattern(cache_pattern, "*")

    def _invalidate_memory_pattern(self, prefix: str, pattern: str) -> None:
        """Invalidate keys matching pattern in memory caches.

        Args:
            prefix: Key prefix to match
            pattern: Pattern to match (supports * wildcard)
        """
        import fnmatch

        # Helper to check if key matches pattern
        def matches_pattern(key: str, prefix: str, pattern: str) -> bool:
            if not key.startswith(prefix):
                return False

            if pattern == "*":
                return True

            # Extract the part after prefix for pattern matching
            key_suffix = key[len(prefix) :]

            # Convert pattern to fnmatch pattern
            # Handle hierarchical patterns like "llm.*"
            if pattern.endswith("*"):
                # Match anything starting with the prefix
                pattern_prefix = pattern[:-1]
                return key_suffix.startswith(pattern_prefix)
            else:
                # Exact match or complex pattern
                return fnmatch.fnmatch(key_suffix, pattern)

        # Invalidate in cache
        try:
            # Access the underlying cache dict for memory backend
            if hasattr(self.cache.backend, "_cache"):
                cache_dict = self.cache.backend._cache
                keys_to_delete = [
                    k
                    for k in cache_dict.keys()
                    if matches_pattern(k, prefix, pattern)
                ]
                for key in keys_to_delete:
                    del cache_dict[key]
                logger.debug(
                    f"Invalidated {len(keys_to_delete)} keys from cache"
                )
        except Exception as e:
            logger.warning(f"Failed to invalidate cache pattern: {e}")

    def get_metrics(self) -> Dict[str, Union[int, float]]:
        """Get cache performance metrics.

        Returns:
            Dictionary of cache metrics
        """
        total_requests = self.metrics["hits"] + self.metrics["misses"]

        return {
            "total_hits": self.metrics["hits"],
            "total_misses": self.metrics["misses"],
            "total_errors": self.metrics["errors"],
            "total_requests": total_requests,
            "hit_rate": self.metrics["hits"] / total_requests
            if total_requests > 0
            else 0,
        }


# Compatibility alias for existing code
TwoTierCache = SingleTierCache


def get_thread_local_cache() -> SingleTierCache:
    """Get or create thread-local cache instance.

    Returns:
        Thread-local SingleTierCache instance
    """
    if not hasattr(thread_local, "cache"):
        thread_local.cache = SingleTierCache()
    return thread_local.cache
