"""
Production Rate Limiter for Dynamics 365
Implements dual-bucket rate limiting for burst and service protection limits
"""

import asyncio
import time
import logging
from datetime import datetime, timedelta
from typing import Dict, Any, Optional
from dataclasses import dataclass, field

logger = logging.getLogger(__name__)


@dataclass
class RateLimitBucket:
    """Single rate limit bucket"""

    capacity: int
    refill_rate: float  # tokens per second
    tokens: float = field(init=False)
    last_refill: float = field(init=False)

    def __post_init__(self):
        self.tokens = float(self.capacity)
        self.last_refill = time.monotonic()

    def refill(self) -> float:
        """Refill bucket based on elapsed time"""
        now = time.monotonic()
        elapsed = now - self.last_refill

        # Add tokens based on refill rate
        tokens_to_add = elapsed * self.refill_rate
        self.tokens = min(self.capacity, self.tokens + tokens_to_add)
        self.last_refill = now

        return self.tokens

    def consume(self, tokens: int = 1) -> bool:
        """Try to consume tokens, return True if successful"""
        self.refill()

        if self.tokens >= tokens:
            self.tokens -= tokens
            return True
        return False

    def time_until_available(self, tokens: int = 1) -> float:
        """Calculate seconds until tokens will be available"""
        self.refill()

        if self.tokens >= tokens:
            return 0.0

        deficit = tokens - self.tokens
        return deficit / self.refill_rate


class DynamicsRateLimiter:
    """
    Dual-bucket rate limiter for Dynamics 365

    Implements:
    - Burst limit: 60 requests per second
    - Service Protection: 6000 requests per 5 minutes
    - Per-user limits with window tracking
    """

    def __init__(self):
        # Burst bucket - 60 requests per second with burst of 100
        self.burst_bucket = RateLimitBucket(capacity=100, refill_rate=60.0)

        # Service protection bucket - 6000 per 5 minutes
        self.service_bucket = RateLimitBucket(
            capacity=6000, refill_rate=20.0
        )  # 6000/300s

        # Lock for thread safety
        self._lock = asyncio.Lock()

        # Track window reset time from headers
        self.window_reset_time: Optional[datetime] = None
        self.last_remaining_count: Optional[int] = None

        # Retry-After tracking
        self.retry_after_until: Optional[datetime] = None

    async def acquire(self, tokens: int = 1) -> None:
        """
        Acquire tokens from both buckets, waiting if necessary

        Args:
            tokens: Number of tokens to acquire
        """
        async with self._lock:
            while True:
                # Check if we're in a retry-after period
                if self.retry_after_until:
                    if datetime.utcnow() < self.retry_after_until:
                        wait_seconds = (
                            self.retry_after_until - datetime.utcnow()
                        ).total_seconds()
                        logger.warning(
                            f"Rate limited by Retry-After for {wait_seconds:.1f}s"
                        )
                        await asyncio.sleep(wait_seconds)
                        self.retry_after_until = None
                    else:
                        self.retry_after_until = None

                # Try to consume from both buckets
                burst_ready = self.burst_bucket.consume(tokens)
                service_ready = self.service_bucket.consume(tokens)

                if burst_ready and service_ready:
                    # Both buckets have capacity
                    return

                # Calculate wait time
                burst_wait = (
                    self.burst_bucket.time_until_available(tokens)
                    if not burst_ready
                    else 0
                )
                service_wait = (
                    self.service_bucket.time_until_available(tokens)
                    if not service_ready
                    else 0
                )
                wait_time = max(burst_wait, service_wait)

                if wait_time > 0:
                    logger.debug(
                        f"Rate limiter waiting {wait_time:.2f}s for {tokens} tokens"
                    )
                    await asyncio.sleep(wait_time)

                    # Refund tokens if only one bucket was ready
                    if burst_ready and not service_ready:
                        self.burst_bucket.tokens += tokens
                    elif service_ready and not burst_ready:
                        self.service_bucket.tokens += tokens

    def update_from_headers(self, headers: Dict[str, str]) -> None:
        """
        Update rate limiter state from response headers

        Headers to parse:
        - x-ms-ratelimit-burst-remaining-xrm-requests
        - x-ms-ratelimit-time-remaining-xrm-requests
        - Retry-After
        """
        # Update burst remaining
        burst_remaining = headers.get("x-ms-ratelimit-burst-remaining-xrm-requests")
        if burst_remaining:
            try:
                remaining = int(burst_remaining)
                # Sync burst bucket with server state
                self.burst_bucket.tokens = min(remaining, self.burst_bucket.tokens)
                logger.debug(f"Burst tokens remaining: {remaining}")
            except ValueError:
                pass

        # Update service protection window
        time_remaining = headers.get("x-ms-ratelimit-time-remaining-xrm-requests")
        if time_remaining:
            try:
                # Format: "00:04:59" (HH:MM:SS)
                parts = time_remaining.split(":")
                if len(parts) == 3:
                    hours, minutes, seconds = map(int, parts)
                    total_seconds = hours * 3600 + minutes * 60 + seconds
                    self.window_reset_time = datetime.utcnow() + timedelta(
                        seconds=total_seconds
                    )
                    logger.debug(
                        f"Service protection window resets in {total_seconds}s"
                    )
            except (ValueError, IndexError):
                pass

        # Handle Retry-After header
        retry_after = headers.get("Retry-After")
        if retry_after:
            try:
                # Could be seconds or HTTP date
                if retry_after.isdigit():
                    seconds = int(retry_after)
                    self.retry_after_until = datetime.utcnow() + timedelta(
                        seconds=seconds
                    )
                    logger.warning(f"Retry-After header: wait {seconds} seconds")
                else:
                    # Parse HTTP date format
                    from email.utils import parsedate_to_datetime

                    self.retry_after_until = parsedate_to_datetime(retry_after)
                    wait_seconds = (
                        self.retry_after_until - datetime.utcnow()
                    ).total_seconds()
                    logger.warning(
                        f"Retry-After header: wait until {retry_after} ({wait_seconds:.1f}s)"
                    )
            except Exception as e:
                logger.error(
                    f"Failed to parse Retry-After header: {retry_after} - {str(e)}"
                )

    def get_status(self) -> Dict[str, Any]:
        """Get current rate limiter status"""
        return {
            "burst_tokens": self.burst_bucket.tokens,
            "burst_capacity": self.burst_bucket.capacity,
            "service_tokens": self.service_bucket.tokens,
            "service_capacity": self.service_bucket.capacity,
            "window_reset_time": self.window_reset_time.isoformat()
            if self.window_reset_time
            else None,
            "retry_after_until": self.retry_after_until.isoformat()
            if self.retry_after_until
            else None,
        }
