"""
LLM caching protocol and reference implementations.

This module provides the cache protocol (interface) that services can implement
with their own caching strategies (Redis, SQLite, etc.). The library provides
only the mechanism; services implement the policy.
"""

from typing import Protocol, Optional, Any, Dict
from abc import abstractmethod
from datetime import datetime, timedelta
import logging

logger = logging.getLogger(__name__)


class LLMCache(Protocol):
    """
    Protocol for LLM caching implementations.

    Services can implement this interface with their own caching strategies.
    The library doesn't care HOW you cache, just that you follow this interface.

    Example implementations:
    - RedisCache: Distributed cache using Redis
    - SQLiteCache: Single-instance persistent cache
    - InMemoryCache: Development/testing cache
    """

    @abstractmethod
    async def get(self, key: str) -> Optional[Any]:
        """
        Retrieve cached response.

        Args:
            key: Cache key (generated by caller)

        Returns:
            Cached response or None if miss
        """
        ...

    @abstractmethod
    async def set(self, key: str, value: Any, ttl_seconds: Optional[int] = None):
        """
        Store response in cache.

        Args:
            key: Cache key
            value: Response to cache
            ttl_seconds: Optional TTL (cache implementation decides default)
        """
        ...

    @abstractmethod
    async def delete(self, key: str):
        """Delete cache entry."""
        ...

    @abstractmethod
    async def clear(self):
        """Clear all cache entries (optional, may no-op)."""
        ...


class InMemoryCache:
    """
    Simple in-memory cache for development/testing.

    WARNING: This is NOT for production use:
    - Not shared across processes
    - Lost on restart
    - No size limits
    - Basic TTL enforcement

    Services should implement their own cache for production.
    """

    def __init__(self):
        self._cache: Dict[str, Any] = {}
        self._metadata: Dict[str, Dict[str, Any]] = {}

    async def get(self, key: str) -> Optional[Any]:
        """Retrieve from in-memory cache."""
        if key in self._cache:
            # Check TTL
            meta = self._metadata.get(key, {})
            cached_at = meta.get("cached_at")
            ttl_seconds = meta.get("ttl_seconds")

            if cached_at and ttl_seconds:
                expiry = cached_at + timedelta(seconds=ttl_seconds)
                if datetime.now() > expiry:
                    logger.debug(f"InMemoryCache expired: {key[:16]}...")
                    self._cache.pop(key)
                    self._metadata.pop(key)
                    return None

            logger.debug(f"InMemoryCache hit: {key[:16]}...")
            return self._cache[key]

        logger.debug(f"InMemoryCache miss: {key[:16]}...")
        return None

    async def set(self, key: str, value: Any, ttl_seconds: Optional[int] = None):
        """Store in in-memory cache."""
        self._cache[key] = value
        self._metadata[key] = {
            "cached_at": datetime.now(),
            "ttl_seconds": ttl_seconds,
        }
        logger.debug(
            f"InMemoryCache stored: {key[:16]}... "
            f"(TTL: {ttl_seconds}s, total entries: {len(self._cache)})"
        )

    async def delete(self, key: str):
        """Delete from cache."""
        self._cache.pop(key, None)
        self._metadata.pop(key, None)
        logger.debug(f"InMemoryCache deleted: {key[:16]}...")

    async def clear(self):
        """Clear all cache entries."""
        count = len(self._cache)
        self._cache.clear()
        self._metadata.clear()
        logger.info(f"InMemoryCache cleared ({count} entries)")

    def size(self) -> int:
        """Return number of cached entries."""
        return len(self._cache)

    def get_stats(self) -> Dict[str, Any]:
        """Get cache statistics."""
        now = datetime.now()
        valid_entries = 0
        expired_entries = 0

        for key, meta in self._metadata.items():
            cached_at = meta.get("cached_at")
            ttl_seconds = meta.get("ttl_seconds")

            if cached_at and ttl_seconds:
                expiry = cached_at + timedelta(seconds=ttl_seconds)
                if now > expiry:
                    expired_entries += 1
                else:
                    valid_entries += 1
            else:
                valid_entries += 1

        return {
            "total_entries": len(self._cache),
            "valid_entries": valid_entries,
            "expired_entries": expired_entries,
            "cache_type": "in_memory",
        }
