"""
Caching system for HTTP responses and tool results
"""

import asyncio
import hashlib
import json
import threading
import time
from collections import OrderedDict
from typing import Any, Dict, Optional, Tuple


class LRUCache:
    """Thread-safe LRU cache with TTL support."""

    def __init__(self, max_size: int = 1000, default_ttl: int = 300):
        """
        Initialize LRU cache.

        Args:
            max_size: Maximum number of items in cache
            default_ttl: Default time-to-live in seconds
        """
        self.max_size = max_size
        self.default_ttl = default_ttl
        self.cache: OrderedDict = OrderedDict()
        self.lock = threading.RLock()

    def _make_key(self, key_components: tuple) -> str:
        """Generate cache key from components."""
        key_str = json.dumps(key_components, sort_keys=True, default=str)
        return hashlib.md5(key_str.encode()).hexdigest()

    def get(self, key: str, default: Any = None) -> Optional[Any]:
        """Get value from cache."""
        with self.lock:
            if key not in self.cache:
                return default

            item = self.cache[key]
            if self._is_expired(item):
                del self.cache[key]
                return default

            # Move to end (most recently used)
            self.cache.move_to_end(key)
            return item["value"]

    def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
        """Set value in cache."""
        with self.lock:
            if key in self.cache:
                del self.cache[key]

            ttl_value = ttl if ttl is not None else self.default_ttl

            self.cache[key] = {
                "value": value,
                "timestamp": time.time(),
                "ttl": ttl_value,
            }

            # Remove oldest items if cache is full
            while len(self.cache) > self.max_size:
                self.cache.popitem(last=False)

    def delete(self, key: str) -> bool:
        """Delete key from cache."""
        with self.lock:
            if key in self.cache:
                del self.cache[key]
                return True
            return False

    def clear(self) -> None:
        """Clear all cache."""
        with self.lock:
            self.cache.clear()

    def size(self) -> int:
        """Get current cache size."""
        with self.lock:
            return len(self.cache)

    def _is_expired(self, item: dict) -> bool:
        """Check if cache item is expired."""
        return time.time() - item["timestamp"] > item["ttl"]

    def cleanup_expired(self) -> int:
        """Remove expired items and return count removed."""
        with self.lock:
            expired_keys = [key for key, item in self.cache.items() if self._is_expired(item)]

            for key in expired_keys:
                del self.cache[key]

            return len(expired_keys)

    def get_stats(self) -> dict[str, Any]:
        """Get cache statistics."""
        with self.lock:
            total_items = len(self.cache)
            expired_items = sum(1 for item in self.cache.values() if self._is_expired(item))

            return {
                "total_items": total_items,
                "expired_items": expired_items,
                "max_size": self.max_size,
                "default_ttl": self.default_ttl,
                "hit_rate": 0.0,  # Would need hit/miss counters
                "memory_usage": "unknown",  # Would need size calculation
            }


class HTTPCache:
    """Specialized cache for HTTP responses."""

    def __init__(self, cache: LRUCache):
        self.cache = cache

    def make_cache_key(
        self,
        method: str,
        url: str,
        params: Optional[dict] = None,
        headers: Optional[dict] = None,
    ) -> str:
        """Generate cache key for HTTP request."""
        key_components = (
            method.upper(),
            url,
            params or {},
            {k.lower(): v for k, v in (headers or {}).items()},  # Normalize headers
        )
        return self.cache._make_key(key_components)

    def get_response(
        self,
        method: str,
        url: str,
        params: Optional[dict] = None,
        headers: Optional[dict] = None,
    ) -> Optional[dict]:
        """Get cached HTTP response."""
        key = self.make_cache_key(method, url, params, headers)
        return self.cache.get(key)

    def set_response(
        self,
        method: str,
        url: str,
        response: dict,
        params: Optional[dict] = None,
        headers: Optional[dict] = None,
        ttl: Optional[int] = None,
    ) -> None:
        """Cache HTTP response."""
        key = self.make_cache_key(method, url, params, headers)
        self.cache.set(key, response, ttl)

    def is_cacheable_response(self, response: dict) -> bool:
        """Check if response should be cached."""
        # Don't cache error responses
        if not response.get("success", False):
            return False

        # Don't cache non-200 responses
        if response.get("status_code") != 200:
            return False

        # Don't cache large responses (>1MB)
        if response.get("size", 0) > 1024 * 1024:
            return False

        return True


class AsyncHTTPCache(HTTPCache):
    """Async version of HTTP cache."""

    def __init__(self, cache: LRUCache):
        super().__init__(cache)

    async def get_response_async(
        self,
        method: str,
        url: str,
        params: Optional[dict] = None,
        headers: Optional[dict] = None,
    ) -> Optional[dict]:
        """Async get cached HTTP response."""
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(None, self.get_response, method, url, params, headers)

    async def set_response_async(
        self,
        method: str,
        url: str,
        response: dict,
        params: Optional[dict] = None,
        headers: Optional[dict] = None,
        ttl: Optional[int] = None,
    ) -> None:
        """Async cache HTTP response."""
        loop = asyncio.get_event_loop()
        await loop.run_in_executor(
            None, self.set_response, method, url, response, params, headers, ttl
        )


# Global cache instances
_response_cache = LRUCache(max_size=500, default_ttl=300)  # 5 minutes default
_http_cache = HTTPCache(_response_cache)
_async_http_cache = AsyncHTTPCache(_response_cache)


def get_response_cache() -> LRUCache:
    """Get global response cache."""
    return _response_cache


def get_http_cache() -> HTTPCache:
    """Get global HTTP cache."""
    return _http_cache


def get_async_http_cache() -> AsyncHTTPCache:
    """Get global async HTTP cache."""
    return _async_http_cache


def cleanup_cache() -> int:
    """Cleanup expired cache entries globally."""
    return _response_cache.cleanup_expired()


def clear_cache() -> None:
    """Clear all cache entries."""
    _response_cache.clear()


def get_cache_stats() -> dict[str, Any]:
    """Get global cache statistics."""
    return _response_cache.get_stats()
