"""LRU Cache implementation for PromptCanvas SDK."""

import time
from collections import OrderedDict
from threading import Lock
from typing import Optional, Dict

from .models import Prompt, CachedPrompt, CacheStats


class LRUCache:
    """
    Thread-safe LRU cache with TTL support.
    
    Uses OrderedDict for O(1) access and eviction.
    """

    def __init__(self, max_size: int = 1000, ttl_seconds: float = 300.0):
        """
        Initialize the cache.
        
        Args:
            max_size: Maximum number of entries to cache
            ttl_seconds: Time-to-live in seconds (default: 5 minutes)
        """
        self._cache: OrderedDict[str, CachedPrompt] = OrderedDict()
        self._max_size = max_size
        self._ttl_seconds = ttl_seconds
        self._lock = Lock()
        self._hits = 0
        self._misses = 0

    def get(self, key: str) -> Optional[CachedPrompt]:
        """
        Get a cached entry, moving it to the end (most recently used).
        
        Returns None if not found or expired.
        """
        with self._lock:
            if key not in self._cache:
                self._misses += 1
                return None

            entry = self._cache[key]
            
            # Check expiration
            if time.time() > entry.expires_at:
                del self._cache[key]
                self._misses += 1
                return None

            # Move to end (most recently used)
            self._cache.move_to_end(key)
            self._hits += 1
            return entry

    def set(self, key: str, prompt: Prompt) -> None:
        """Store a prompt in the cache."""
        with self._lock:
            now = time.time()
            entry = CachedPrompt(
                prompt=prompt,
                fetched_at=now,
                expires_at=now + self._ttl_seconds,
            )

            if key in self._cache:
                self._cache[key] = entry
                self._cache.move_to_end(key)
            else:
                # Evict if at capacity
                while len(self._cache) >= self._max_size:
                    self._cache.popitem(last=False)
                self._cache[key] = entry

    def delete(self, key: str) -> bool:
        """Remove an entry from the cache."""
        with self._lock:
            if key in self._cache:
                del self._cache[key]
                return True
            return False

    def clear(self) -> None:
        """Clear all cached entries."""
        with self._lock:
            self._cache.clear()
            self._hits = 0
            self._misses = 0

    def get_stats(self) -> CacheStats:
        """Get cache statistics."""
        with self._lock:
            total = self._hits + self._misses
            return CacheStats(
                hits=self._hits,
                misses=self._misses,
                hit_rate=self._hits / total if total > 0 else 0.0,
                size=len(self._cache),
                max_size=self._max_size,
            )

    @property
    def size(self) -> int:
        """Current number of cached entries."""
        with self._lock:
            return len(self._cache)

    def is_stale(self, entry: CachedPrompt) -> bool:
        """Check if a cached entry is expired."""
        return time.time() > entry.expires_at

    def is_within_grace_period(
        self, entry: CachedPrompt, grace_seconds: float = 60.0
    ) -> bool:
        """Check if entry is within stale-while-revalidate grace period."""
        return time.time() < entry.expires_at + grace_seconds


class AsyncLRUCache(LRUCache):
    """
    Async-compatible LRU cache.
    
    Uses the same implementation but provides async method aliases
    for consistency with async code.
    """

    async def aget(self, key: str) -> Optional[CachedPrompt]:
        """Async get - delegates to sync implementation."""
        return self.get(key)

    async def aset(self, key: str, prompt: Prompt) -> None:
        """Async set - delegates to sync implementation."""
        self.set(key, prompt)

    async def adelete(self, key: str) -> bool:
        """Async delete - delegates to sync implementation."""
        return self.delete(key)

    async def aclear(self) -> None:
        """Async clear - delegates to sync implementation."""
        self.clear()
