"""
LRU cache for player records with auto-save on eviction.

Prevents unbounded memory growth by evicting inactive players.
"""

from collections import OrderedDict
from typing import Optional, Callable
import sys
import logging
import asyncio

logger = logging.getLogger(__name__)


class PlayerCache:
    """
    LRU cache for player records.

    Features:
    - Automatic eviction of least-recently-used players
    - Never evicts connected players
    - Auto-save on eviction (optional)
    - Memory usage tracking

    Usage:
        cache = PlayerCache(max_size=100)
        cache.set("user123", player)
        player = cache.get("user123")
    """

    def __init__(
        self,
        max_size: int = 100,
        auto_save: bool = True
    ):
        """
        Initialize player cache.

        Args:
            max_size: Maximum number of players to keep in memory
            auto_save: Whether to auto-save evicted players
        """
        self._cache: OrderedDict = OrderedDict()
        self._max_size = max_size
        self._auto_save = auto_save
        self.on_evict: Optional[Callable] = None  # Callback for eviction

        logger.info(f"PlayerCache initialized: max_size={max_size}, auto_save={auto_save}")

    def get(self, user_id: str):
        """
        Get player from cache (marks as recently used).

        Args:
            user_id: User ID

        Returns:
            playerClass instance or None
        """
        if user_id in self._cache:
            # Move to end (mark as recently used)
            self._cache.move_to_end(user_id)
            return self._cache[user_id]
        return None

    def set(self, user_id: str, player) -> None:
        """
        Add player to cache.

        Args:
            user_id: User ID
            player: playerClass instance
        """
        # If already exists, update and mark as recently used
        if user_id in self._cache:
            self._cache.move_to_end(user_id)
            self._cache[user_id] = player
            return

        # Check if cache is full
        if len(self._cache) >= self._max_size:
            self._evict_lru()

        # Add new player
        self._cache[user_id] = player

    def remove(self, user_id: str) -> bool:
        """
        Remove player from cache.

        Args:
            user_id: User ID

        Returns:
            True if removed, False if not found
        """
        if user_id in self._cache:
            del self._cache[user_id]
            return True
        return False

    def _evict_lru(self) -> None:
        """Evict least recently used player (unless connected)"""
        # Find first disconnected player (from oldest to newest)
        for user_id, player in list(self._cache.items()):
            if player.connection == 'disconnected':
                logger.info(f"Evicting player from cache: {user_id} ({player.c.firstname})")

                # Save if auto_save enabled
                if self._auto_save:
                    self._save_player(player)

                # Call eviction callback
                if self.on_evict:
                    asyncio.create_task(self.on_evict(player))

                # Remove from cache
                del self._cache[user_id]
                return

        # If all players are connected, log warning but don't evict
        logger.warning(f"PlayerCache full ({self._max_size}) with all connected players")

    def _save_player(self, player) -> None:
        """Save player to database (async)"""
        from functions import saveGameAsync

        try:
            asyncio.create_task(saveGameAsync(player))
        except Exception as e:
            logger.error(f"Failed to save evicted player {player.id}: {e}")

    def size(self) -> int:
        """Get current cache size"""
        return len(self._cache)

    def is_full(self) -> bool:
        """Check if cache is full"""
        return len(self._cache) >= self._max_size

    def estimate_memory_mb(self) -> float:
        """
        Estimate memory usage of cached players.

        Returns:
            Estimated memory in MB
        """
        if not self._cache:
            return 0.0

        # Sample one player to estimate average size
        sample_player = next(iter(self._cache.values()))
        sample_size = sys.getsizeof(sample_player)

        # Rough estimate including nested objects
        # Multiplier accounts for nested personClass, lists, etc.
        estimated_total = sample_size * len(self._cache) * 10

        return estimated_total / (1024 * 1024)  # Convert to MB

    def get_stats(self) -> dict:
        """Get cache statistics"""
        connected = sum(1 for p in self._cache.values() if p.connection == 'connected')
        disconnected = len(self._cache) - connected

        return {
            'size': len(self._cache),
            'max_size': self._max_size,
            'connected': connected,
            'disconnected': disconnected,
            'memory_mb': self.estimate_memory_mb(),
            'utilization': len(self._cache) / self._max_size * 100,
        }
