"""Cost tracking, response caching, and rate limiting for LLM integration.

Provides ``CostTracker`` for API usage monitoring, ``ResponseCache`` for
avoiding duplicate API calls, and ``RateLimiter`` for request throttling.
"""

from __future__ import annotations

import hashlib
import json
import time
from dataclasses import dataclass, field
from threading import Lock
from typing import Any

from oscura.api.integrations.llm.types import TOKEN_COSTS


@dataclass
class CostTracker:
    """Tracks API usage costs.

    Attributes:
        total_input_tokens: Total input tokens used across all requests
        total_output_tokens: Total output tokens used across all requests
        total_cost: Total estimated cost in USD
        request_count: Number of API requests made
    """

    total_input_tokens: int = 0
    total_output_tokens: int = 0
    total_cost: float = 0.0
    request_count: int = 0
    _lock: Lock = field(default_factory=Lock)

    def record(self, model: str, input_tokens: int, output_tokens: int) -> float:
        """Record token usage and return estimated cost.

        Args:
            model: Model name for cost lookup
            input_tokens: Number of input/prompt tokens
            output_tokens: Number of output/completion tokens

        Returns:
            Estimated cost in USD for this request
        """
        # Get cost rates for model, fall back to default
        rates = TOKEN_COSTS.get(model, TOKEN_COSTS["default"])

        cost = input_tokens / 1000 * rates["input"] + output_tokens / 1000 * rates["output"]

        with self._lock:
            self.total_input_tokens += input_tokens
            self.total_output_tokens += output_tokens
            self.total_cost += cost
            self.request_count += 1

        return cost

    def reset(self) -> None:
        """Reset all tracking counters."""
        with self._lock:
            self.total_input_tokens = 0
            self.total_output_tokens = 0
            self.total_cost = 0.0
            self.request_count = 0

    def get_summary(self) -> dict[str, Any]:
        """Get summary of usage statistics.

        Returns:
            Dictionary with usage statistics
        """
        with self._lock:
            return {
                "total_input_tokens": self.total_input_tokens,
                "total_output_tokens": self.total_output_tokens,
                "total_tokens": self.total_input_tokens + self.total_output_tokens,
                "total_cost_usd": round(self.total_cost, 6),
                "request_count": self.request_count,
                "avg_cost_per_request": (
                    round(self.total_cost / self.request_count, 6)
                    if self.request_count > 0
                    else 0.0
                ),
            }


class ResponseCache:
    """Simple LRU cache for LLM responses.

    Caches responses based on prompt hash to avoid repeated API calls
    for identical queries. Thread-safe implementation.
    """

    def __init__(self, max_size: int = 100, ttl_seconds: float = 3600.0) -> None:
        """Initialize response cache.

        Args:
            max_size: Maximum number of cached responses
            ttl_seconds: Time-to-live for cache entries in seconds
        """
        self.max_size = max_size
        self.ttl_seconds = ttl_seconds
        self._cache: dict[str, tuple[Any, float]] = {}
        self._lock = Lock()

    def _make_key(self, prompt: str, model: str, **kwargs: Any) -> str:
        """Create cache key from request parameters.

        Args:
            prompt: The prompt text
            model: Model name
            **kwargs: Additional parameters affecting response

        Returns:
            Hash key for cache lookup
        """
        key_data = json.dumps(
            {"prompt": prompt, "model": model, "kwargs": sorted(kwargs.items())},
            sort_keys=True,
        )
        return hashlib.sha256(key_data.encode()).hexdigest()

    def get(self, prompt: str, model: str, **kwargs: Any) -> Any | None:
        """Get cached response if available and not expired.

        Args:
            prompt: The prompt text
            model: Model name
            **kwargs: Additional parameters

        Returns:
            Cached response or None if not found/expired
        """
        key = self._make_key(prompt, model, **kwargs)

        with self._lock:
            if key in self._cache:
                response, timestamp = self._cache[key]
                if time.time() - timestamp < self.ttl_seconds:
                    return response
                # Expired entry
                del self._cache[key]
            return None

    def set(self, prompt: str, model: str, response: Any, **kwargs: Any) -> None:
        """Cache a response.

        Args:
            prompt: The prompt text
            model: Model name
            response: Response to cache
            **kwargs: Additional parameters
        """
        key = self._make_key(prompt, model, **kwargs)

        with self._lock:
            # Evict oldest entries if at capacity
            while len(self._cache) >= self.max_size:
                oldest_key = min(self._cache.keys(), key=lambda k: self._cache[k][1])
                del self._cache[oldest_key]

            self._cache[key] = (response, time.time())

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

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


class RateLimiter:
    """Rate limiter for API requests.

    Implements token bucket algorithm for rate limiting.
    """

    def __init__(self, requests_per_minute: int = 60) -> None:
        """Initialize rate limiter.

        Args:
            requests_per_minute: Maximum requests allowed per minute
        """
        self.requests_per_minute = requests_per_minute
        self.min_interval = 60.0 / requests_per_minute if requests_per_minute > 0 else 0
        self.last_request_time = 0.0
        self.lock = Lock()

    def acquire(self) -> None:
        """Wait if necessary to respect rate limit."""
        if self.requests_per_minute <= 0:
            return  # No rate limiting

        with self.lock:
            now = time.time()
            time_since_last = now - self.last_request_time
            if time_since_last < self.min_interval:
                sleep_time = self.min_interval - time_since_last
                time.sleep(sleep_time)
            self.last_request_time = time.time()


# Global instances for tracking
_global_cost_tracker = CostTracker()
_global_response_cache = ResponseCache()


def get_cost_tracker() -> CostTracker:
    """Get global cost tracker instance.

    Returns:
        Global CostTracker for monitoring API costs
    """
    return _global_cost_tracker


def get_response_cache() -> ResponseCache:
    """Get global response cache instance.

    Returns:
        Global ResponseCache for caching LLM responses
    """
    return _global_response_cache
