"""
Fix Cache - Local cache for active fixes.

Provides fast lookup of fixes with TTL-based expiration.
"""

from __future__ import annotations

import threading
import time
from dataclasses import dataclass, field
from typing import Dict, List, Optional

from risicare.runtime.config import ActiveFix, FixRuntimeConfig


@dataclass
class CacheEntry:
    """A cached fix entry with expiration."""

    fix: ActiveFix
    created_at: float
    expires_at: float

    def is_expired(self) -> bool:
        """Check if entry has expired."""
        return time.time() > self.expires_at


@dataclass
class CacheStats:
    """Statistics about cache usage."""

    hits: int = 0
    misses: int = 0
    evictions: int = 0
    size: int = 0
    last_refresh: Optional[float] = None

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


class FixCache:
    """
    LRU cache for active fixes.

    Thread-safe cache with TTL-based expiration.
    Fixes are keyed by error code for fast lookup.
    """

    def __init__(self, config: Optional[FixRuntimeConfig] = None) -> None:
        """
        Initialize the cache.

        Args:
            config: Runtime configuration.
        """
        self._config = config or FixRuntimeConfig()
        self._cache: Dict[str, CacheEntry] = {}
        self._lock = threading.RLock()
        self._stats = CacheStats()

    @property
    def enabled(self) -> bool:
        """Check if caching is enabled."""
        return self._config.cache_enabled

    def get(self, error_code: str) -> Optional[ActiveFix]:
        """
        Get a fix by error code.

        Checks both exact matches and wildcard matches.

        Args:
            error_code: Error code to look up.

        Returns:
            ActiveFix if found and not expired, None otherwise.
        """
        if not self.enabled:
            return None

        with self._lock:
            # Try exact match first
            entry = self._cache.get(error_code)
            if entry and not entry.is_expired():
                self._stats.hits += 1
                return entry.fix

            # Try wildcard matches
            for cached_code, entry in list(self._cache.items()):
                if entry.is_expired():
                    del self._cache[cached_code]
                    self._stats.evictions += 1
                    continue

                if entry.fix.matches_error(error_code):
                    self._stats.hits += 1
                    return entry.fix

            self._stats.misses += 1
            return None

    def get_all(self) -> List[ActiveFix]:
        """
        Get all non-expired fixes.

        Returns:
            List of active fixes.
        """
        with self._lock:
            now = time.time()
            valid = []
            expired_keys = []

            for key, entry in self._cache.items():
                if entry.expires_at > now:
                    valid.append(entry.fix)
                else:
                    expired_keys.append(key)

            # Clean up expired entries
            for key in expired_keys:
                del self._cache[key]
                self._stats.evictions += 1

            return valid

    def set(self, fix: ActiveFix) -> None:
        """
        Add a fix to the cache.

        Args:
            fix: Fix to cache.
        """
        if not self.enabled:
            return

        with self._lock:
            now = time.time()
            entry = CacheEntry(
                fix=fix,
                created_at=now,
                expires_at=now + self._config.cache_ttl_seconds,
            )

            # Evict if at capacity
            if len(self._cache) >= self._config.cache_max_entries:
                self._evict_oldest()

            self._cache[fix.error_code] = entry
            self._stats.size = len(self._cache)

    def set_all(self, fixes: List[ActiveFix]) -> None:
        """
        Replace all cached fixes.

        Clears the cache and adds the new fixes.

        Args:
            fixes: List of fixes to cache.
        """
        if not self.enabled:
            return

        with self._lock:
            self._cache.clear()
            now = time.time()

            for fix in fixes:
                if len(self._cache) >= self._config.cache_max_entries:
                    break

                entry = CacheEntry(
                    fix=fix,
                    created_at=now,
                    expires_at=now + self._config.cache_ttl_seconds,
                )
                self._cache[fix.error_code] = entry

            self._stats.size = len(self._cache)
            self._stats.last_refresh = now

    def invalidate(self, error_code: str) -> bool:
        """
        Invalidate a specific cache entry.

        Args:
            error_code: Error code to invalidate.

        Returns:
            True if entry was found and removed.
        """
        with self._lock:
            if error_code in self._cache:
                del self._cache[error_code]
                self._stats.size = len(self._cache)
                return True
            return False

    def clear(self) -> int:
        """
        Clear all cache entries.

        Returns:
            Number of entries cleared.
        """
        with self._lock:
            count = len(self._cache)
            self._cache.clear()
            self._stats.size = 0
            return count

    def stats(self) -> CacheStats:
        """Get cache statistics."""
        with self._lock:
            self._stats.size = len(self._cache)
            return self._stats

    def _evict_oldest(self) -> None:
        """Evict the oldest cache entry."""
        if not self._cache:
            return

        # Find oldest entry
        oldest_key = None
        oldest_time = float("inf")

        for key, entry in self._cache.items():
            if entry.created_at < oldest_time:
                oldest_time = entry.created_at
                oldest_key = key

        if oldest_key:
            del self._cache[oldest_key]
            self._stats.evictions += 1
