"""Rate limiting: Enforce per-client request quotas to prevent abuse.

Implements token bucket algorithm for fair, configurable rate limiting.

Features:
- Per-client request quotas
- Configurable request rates (requests per second/minute)
- Token bucket refill strategy
- Per-client tracking
- Observable rate limit status
"""

import time
from dataclasses import dataclass
from typing import Dict, Optional, Tuple


@dataclass
class RateLimitConfig:
    """Configuration for rate limiting."""

    requests_per_second: float = 10.0  # Tokens generated per second
    max_burst: int = 100  # Maximum tokens in bucket (burst capacity)
    enforce: bool = True  # Whether to actually enforce limits


class RateLimiter:
    """Token bucket rate limiter for per-client quotas."""

    def __init__(self, config: Optional[RateLimitConfig] = None):
        """
        Initialize rate limiter.

        Args:
            config: Rate limiting configuration
        """
        self.config = config or RateLimitConfig()
        self._buckets: Dict[str, Tuple[float, float]] = {}  # client_id -> (tokens, last_refill_time)

    def _refill_bucket(self, client_id: str, now: float) -> float:
        """
        Refill token bucket based on elapsed time.

        Args:
            client_id: Client identifier
            now: Current time (seconds)

        Returns:
            Current number of tokens in bucket
        """
        if client_id not in self._buckets:
            # New bucket starts at max capacity
            self._buckets[client_id] = (float(self.config.max_burst), now)
            return float(self.config.max_burst)

        tokens, last_refill = self._buckets[client_id]
        elapsed = now - last_refill

        # Add tokens based on elapsed time
        new_tokens = tokens + (elapsed * self.config.requests_per_second)

        # Cap at max_burst
        new_tokens = min(new_tokens, float(self.config.max_burst))

        # Update bucket
        self._buckets[client_id] = (new_tokens, now)

        return new_tokens

    def is_allowed(self, client_id: str) -> bool:
        """
        Check if client is allowed to make a request.

        Args:
            client_id: Client identifier

        Returns:
            True if request is allowed, False if rate limited

        Raises:
            ValueError: If client_id is empty
        """
        if not client_id:
            raise ValueError("client_id required")

        if not self.config.enforce:
            return True

        now = time.time()
        tokens = self._refill_bucket(client_id, now)

        if tokens >= 1.0:
            # Consume one token
            self._buckets[client_id] = (tokens - 1.0, now)
            return True

        return False

    def try_acquire(self, client_id: str, tokens_needed: float = 1.0) -> bool:
        """
        Try to acquire multiple tokens.

        Args:
            client_id: Client identifier
            tokens_needed: Number of tokens to acquire

        Returns:
            True if tokens acquired, False if insufficient
        """
        if not client_id:
            raise ValueError("client_id required")

        if not self.config.enforce:
            return True

        if tokens_needed <= 0:
            raise ValueError("tokens_needed must be positive")

        now = time.time()
        tokens = self._refill_bucket(client_id, now)

        if tokens >= tokens_needed:
            # Consume tokens
            self._buckets[client_id] = (tokens - tokens_needed, now)
            return True

        return False

    def get_status(self, client_id: str) -> Dict[str, any]:
        """
        Get current rate limit status for client.

        Args:
            client_id: Client identifier

        Returns:
            Dict with tokens, refill_rate, next_request_available_at
        """
        if not client_id:
            raise ValueError("client_id required")

        now = time.time()
        tokens = self._refill_bucket(client_id, now)

        # Calculate when next request will be available if bucket is empty
        if tokens < 1.0:
            seconds_until_available = (1.0 - tokens) / self.config.requests_per_second
            next_available = now + seconds_until_available
        else:
            next_available = now

        return {
            "client_id": client_id,
            "tokens_available": tokens,
            "max_burst": self.config.max_burst,
            "refill_rate_per_second": self.config.requests_per_second,
            "next_request_available_at": next_available,
            "enforcement_enabled": self.config.enforce,
        }

    def reset_client(self, client_id: str) -> None:
        """
        Reset rate limit bucket for client (refill to max).

        Args:
            client_id: Client identifier
        """
        if client_id in self._buckets:
            self._buckets[client_id] = (float(self.config.max_burst), time.time())

    def reset_all(self) -> None:
        """Reset all rate limit buckets."""
        self._buckets.clear()

    def get_all_clients(self) -> Dict[str, Dict[str, any]]:
        """Get rate limit status for all active clients."""
        result = {}
        for client_id in self._buckets:
            result[client_id] = self.get_status(client_id)
        return result

    def set_enforce(self, enforce: bool) -> None:
        """Enable/disable rate limit enforcement."""
        self.config.enforce = enforce
