#!/usr/bin/env python
"""
Player Data Caching System
Implements LRU cache for frequently accessed player data to reduce database queries.
"""

import asyncio
from functools import lru_cache
import time
from typing import Optional, Dict, Any
import logging

logger = logging.getLogger(__name__)


class PlayerDataCache:
    """
    LRU cache manager for player data with TTL support.
    Caches frequently accessed player data to reduce database load.
    """

    def __init__(self, maxsize=128, ttl=300):
        """
        Initialize the player data cache.

        Args:
            maxsize: Maximum number of cached entries
            ttl: Time to live in seconds (default: 5 minutes)
        """
        self.maxsize = maxsize
        self.ttl = ttl
        self._cache = {}
        self._timestamps = {}
        self._lock = asyncio.Lock()

    async def get(self, player_id: str) -> Optional[Dict[str, Any]]:
        """
        Retrieve cached player data if valid.

        Args:
            player_id: Player's unique identifier

        Returns:
            Cached player data dict or None if expired/missing
        """
        async with self._lock:
            if player_id not in self._cache:
                return None

            # Check if cache entry has expired
            if time.time() - self._timestamps.get(player_id, 0) > self.ttl:
                await self._invalidate_unlocked(player_id)
                return None

            logger.debug(f"Cache hit for player {player_id}")
            return self._cache[player_id]

    async def set(self, player_id: str, data: Dict[str, Any]):
        """
        Cache player data with current timestamp.

        Args:
            player_id: Player's unique identifier
            data: Player data dictionary to cache
        """
        async with self._lock:
            # Simple LRU: if at capacity, remove oldest entry
            if len(self._cache) >= self.maxsize and player_id not in self._cache:
                oldest = min(self._timestamps.items(), key=lambda x: x[1])
                await self._invalidate_unlocked(oldest[0])

            self._cache[player_id] = data
            self._timestamps[player_id] = time.time()
            logger.debug(f"Cached data for player {player_id}")

    async def invalidate(self, player_id: str):
        """
        Remove player from cache.

        Args:
            player_id: Player's unique identifier
        """
        async with self._lock:
            await self._invalidate_unlocked(player_id)

    async def _invalidate_unlocked(self, player_id: str):
        """
        Remove player from cache (internal, no lock).

        Args:
            player_id: Player's unique identifier
        """
        self._cache.pop(player_id, None)
        self._timestamps.pop(player_id, None)
        logger.debug(f"Invalidated cache for player {player_id}")

    async def clear(self):
        """Clear all cached data."""
        async with self._lock:
            self._cache.clear()
            self._timestamps.clear()
            logger.info("Cleared all cache data")

    async def get_stats(self) -> Dict[str, int]:
        """
        Get cache statistics.

        Returns:
            Dictionary with cache size and capacity info
        """
        async with self._lock:
            return {
                'size': len(self._cache),
                'maxsize': self.maxsize,
                'ttl': self.ttl
            }


# Global cache instance
player_cache = PlayerDataCache(maxsize=256, ttl=300)


@lru_cache(maxsize=128)
def get_location_data(location_id: str):
    """
    Cache location data using functools.lru_cache.
    Location data changes infrequently, so can be cached longer.

    Args:
        location_id: Location identifier

    Returns:
        Location data (to be loaded from database in actual implementation)
    """
    # Placeholder - integrate with actual location loading
    logger.debug(f"Loading location {location_id} (not cached)")
    return None


@lru_cache(maxsize=256)
def get_relationship_data(player_id: str, target_id: str):
    """
    Cache relationship data between two characters.

    Args:
        player_id: First character's ID
        target_id: Second character's ID

    Returns:
        Relationship data tuple
    """
    # Placeholder - integrate with actual relationship loading
    logger.debug(f"Loading relationship {player_id}-{target_id} (not cached)")
    return None


def warm_cache(player_ids: list):
    """
    Pre-warm cache with frequently accessed player data.
    Call this on server startup with active player IDs.

    Args:
        player_ids: List of player IDs to pre-load
    """
    logger.info(f"Warming cache for {len(player_ids)} players")
    # To be implemented with actual player data loading
    pass


async def clear_all_caches():
    """Clear all function caches and player cache."""
    await player_cache.clear()
    get_location_data.cache_clear()
    get_relationship_data.cache_clear()
    logger.info("Cleared all caches")
