"""Per-API throttle gate support for transient HTTP 429 handling."""

from __future__ import annotations

import threading
import time
from dataclasses import dataclass
from typing import Optional


@dataclass
class GateState:
    """Snapshot state for diagnostics and tests."""

    api_name: str
    blocked_until_epoch: float
    is_blocked: bool
    last_retry_after_seconds: Optional[int]


class ThrottleGate:
    """Thread-safe per-API gate that pauses workers until the block window elapses."""

    def __init__(self, api_name: str) -> None:
        self.api_name = api_name
        self._lock = threading.Lock()
        self._blocked_until_epoch = 0.0
        self._last_retry_after_seconds: Optional[int] = None

    def apply_retry_after(self, retry_after_seconds: int) -> float:
        """Apply retry-after duration and keep the maximum active block window.

        Returns the current effective blocked-until epoch.
        """
        retry_after_seconds = max(0, int(retry_after_seconds))
        candidate = time.time() + retry_after_seconds

        with self._lock:
            if candidate > self._blocked_until_epoch:
                self._blocked_until_epoch = candidate
            self._last_retry_after_seconds = retry_after_seconds
            return self._blocked_until_epoch

    MAX_GATE_WAIT_SECONDS = 300.0

    def wait_if_blocked(self) -> float:
        """Sleep until the gate opens. Returns the total slept duration in seconds.

        Re-checks the block window after each sleep to handle concurrent
        ``apply_retry_after`` calls that extend the window (avoids TOCTOU).
        Raises ``TimeoutError`` if the total wait exceeds ``MAX_GATE_WAIT_SECONDS``.
        """
        total_slept = 0.0
        while True:
            with self._lock:
                delay = max(0.0, self._blocked_until_epoch - time.time())
            if delay <= 0:
                return total_slept
            if total_slept + delay > self.MAX_GATE_WAIT_SECONDS:
                raise TimeoutError(
                    f"ThrottleGate '{self.api_name}' exceeded maximum wait of "
                    f"{self.MAX_GATE_WAIT_SECONDS}s (slept {total_slept:.1f}s so far)."
                )
            time.sleep(delay)
            total_slept += delay

    def clear(self) -> None:
        """Reset the gate to unblocked state."""
        with self._lock:
            self._blocked_until_epoch = 0.0
            self._last_retry_after_seconds = None

    def state(self) -> GateState:
        """Return immutable snapshot state."""
        with self._lock:
            now = time.time()
            return GateState(
                api_name=self.api_name,
                blocked_until_epoch=self._blocked_until_epoch,
                is_blocked=self._blocked_until_epoch > now,
                last_retry_after_seconds=self._last_retry_after_seconds,
            )
