"""
Public API Rate Limiting / Throttling

This module provides rate limiting for the public API using a
global limit with burst allowance strategy.

Rate Limits:
- Global: 1000 requests per hour per token
- Burst: 100 requests per minute (allows short bursts of traffic)

Response Headers:
- X-RateLimit-Limit: Total requests allowed per hour
- X-RateLimit-Remaining: Requests remaining in current hour
- X-RateLimit-Reset: Unix timestamp when the limit resets
- X-RateLimit-Burst-Limit: Requests allowed per minute
- X-RateLimit-Burst-Remaining: Requests remaining in current minute
"""

import time
from django.core.cache import cache
from rest_framework.throttling import BaseThrottle


class GlobalBurstRateThrottle(BaseThrottle):
    """
    Rate throttle with global hourly limit and burst minute limit.

    This throttle implements a two-tier rate limiting strategy:
    1. Global limit: Maximum requests per hour
    2. Burst limit: Maximum requests per minute (allows short bursts)

    Both limits must be satisfied for the request to proceed.

    The throttle uses Redis/cache to track request counts per token,
    with separate counters for hourly and minute windows.

    Attributes:
        RATE_HOUR: Maximum requests per hour (default: 1000)
        RATE_MINUTE: Maximum requests per minute (default: 100)
    """

    RATE_HOUR = 1000
    RATE_MINUTE = 100
    cache_format = 'api_throttle_{scope}_{token_id}_{window}'

    def __init__(self):
        self.history = {}
        self.now = None
        self.token = None

    def allow_request(self, request, view):
        """
        Check if the request should be allowed.

        Returns True if both hourly and minute limits allow the request.
        Stores rate limit info on the request for header generation.
        """
        self.now = time.time()

        # Get the API token
        self.token = getattr(request, 'api_token', None)
        if self.token is None:
            # No API token, don't throttle (other auth or unauthenticated)
            return True

        # Check for custom rate limit override on token
        hourly_limit = self.token.rate_limit_override or self.RATE_HOUR
        minute_limit = self.RATE_MINUTE

        # Check hourly limit
        hourly_allowed, hourly_remaining, hourly_reset = self._check_rate(
            'hourly',
            hourly_limit,
            3600  # 1 hour in seconds
        )

        # Check minute limit (burst)
        minute_allowed, minute_remaining, minute_reset = self._check_rate(
            'minute',
            minute_limit,
            60  # 1 minute in seconds
        )

        # Store rate limit info for headers
        request.rate_limit_info = {
            'limit': hourly_limit,
            'remaining': hourly_remaining,
            'reset': hourly_reset,
            'burst_limit': minute_limit,
            'burst_remaining': minute_remaining,
        }

        # Must pass both checks
        if not hourly_allowed or not minute_allowed:
            # Determine which limit was exceeded for wait time
            if not hourly_allowed:
                self.wait_time = hourly_reset - self.now
            else:
                self.wait_time = minute_reset - self.now
            return False

        return True

    def _check_rate(self, scope, limit, duration):
        """
        Check if request is within rate limit for the given scope/duration.

        Args:
            scope: 'hourly' or 'minute'
            limit: Maximum requests allowed in the duration
            duration: Time window in seconds

        Returns:
            tuple: (allowed, remaining, reset_timestamp)
        """
        cache_key = self.cache_format.format(
            scope=scope,
            token_id=str(self.token.id),
            window=int(self.now // duration)
        )

        # Get current count from cache
        count = cache.get(cache_key, 0)

        # Calculate remaining and reset time
        remaining = max(0, limit - count - 1)
        reset_timestamp = int((int(self.now // duration) + 1) * duration)

        if count >= limit:
            return False, 0, reset_timestamp

        # Increment counter
        try:
            # Use atomic increment if available
            new_count = cache.incr(cache_key)
        except ValueError:
            # Key doesn't exist, set it
            new_count = 1
            cache.set(cache_key, new_count, timeout=duration + 10)

        return True, max(0, limit - new_count), reset_timestamp

    def wait(self):
        """
        Return the number of seconds to wait before the next request.
        """
        return getattr(self, 'wait_time', 60)


class RateLimitHeadersMixin:
    """
    Mixin for views to add rate limit headers to responses.

    Add this mixin to views that use GlobalBurstRateThrottle to
    automatically include rate limit headers in all responses.

    Usage:
        class MyView(RateLimitHeadersMixin, APIView):
            throttle_classes = [GlobalBurstRateThrottle]
    """

    def finalize_response(self, request, response, *args, **kwargs):
        """Add rate limit headers to the response."""
        response = super().finalize_response(request, response, *args, **kwargs)

        rate_limit_info = getattr(request, 'rate_limit_info', None)
        if rate_limit_info:
            response['X-RateLimit-Limit'] = rate_limit_info['limit']
            response['X-RateLimit-Remaining'] = rate_limit_info['remaining']
            response['X-RateLimit-Reset'] = rate_limit_info['reset']
            response['X-RateLimit-Burst-Limit'] = rate_limit_info['burst_limit']
            response['X-RateLimit-Burst-Remaining'] = rate_limit_info['burst_remaining']

        return response


def get_throttle_response_data(request):
    """
    Get data for a 429 Too Many Requests response.

    Args:
        request: The HTTP request object

    Returns:
        dict: Response data with error details and retry info
    """
    rate_limit_info = getattr(request, 'rate_limit_info', {})
    reset_time = rate_limit_info.get('reset', int(time.time()) + 60)
    retry_after = max(1, reset_time - int(time.time()))

    return {
        'error': 'rate_limit_exceeded',
        'message': 'API rate limit exceeded. Please wait before making more requests.',
        'retry_after': retry_after,
    }
