"""Retry utilities for transient HTTP failures in evaluation flows."""

from __future__ import annotations

import urllib.error
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from typing import Optional

RETRYABLE_HTTP_STATUS_CODES = {429, 503, 504}
MAX_BACKOFF_SECONDS = 60


def is_retryable_status(status_code: Optional[int]) -> bool:
    """Return True for transient HTTP status codes covered by the spec."""
    if status_code is None:
        return False
    return int(status_code) in RETRYABLE_HTTP_STATUS_CODES


def is_timeout_error(exc: Optional[BaseException]) -> bool:
    """Return True if ``exc`` (or anything in its cause/context chain) is a
    socket read/connect timeout.

    Handles both a bare ``TimeoutError`` (``socket.timeout`` is an alias of it
    on Python 3.10+) and the ``urllib.error.URLError`` wrapper urllib raises
    when the underlying socket operation times out. The chain is walked because
    the agent client surfaces these as an ``AgentRequestError`` with the
    original timeout chained via ``from``.
    """
    seen: set[int] = set()
    current: Optional[BaseException] = exc
    while current is not None and id(current) not in seen:
        seen.add(id(current))
        if isinstance(current, TimeoutError):
            return True
        if isinstance(current, urllib.error.URLError) and isinstance(
            current.reason, TimeoutError
        ):
            return True
        current = current.__cause__ or current.__context__
    return False


def get_backoff_seconds(attempt: int) -> int:
    """Return exponential backoff delay capped at MAX_BACKOFF_SECONDS.

    Examples: 2, 4, 8 for attempts 1..3.
    """
    if attempt < 1:
        raise ValueError("attempt must be >= 1")
    return min(2 ** attempt, MAX_BACKOFF_SECONDS)


def get_retry_after_seconds(retry_after_header: Optional[str]) -> Optional[int]:
    """Parse Retry-After header value (delay-seconds or HTTP-date per RFC 7231)."""
    if retry_after_header is None:
        return None

    value = retry_after_header.strip()
    if not value:
        return None

    # Try delay-seconds (integer) first
    try:
        return max(0, int(value))
    except ValueError:
        pass

    # Try HTTP-date format (RFC 7231 §7.1.3)
    try:
        retry_date = parsedate_to_datetime(value)
        now = datetime.now(timezone.utc)
        delta = int((retry_date - now).total_seconds())
        return max(0, delta)
    except (ValueError, TypeError):
        return None
