"""Bounded retry/backoff policy for network operations."""

from __future__ import annotations

import asyncio
import logging
import random
from dataclasses import dataclass

from aiogram.exceptions import (
    TelegramBadRequest,
    TelegramConflictError,
    TelegramForbiddenError,
    TelegramNetworkError,
    TelegramRetryAfter,
    TelegramServerError,
    TelegramUnauthorizedError,
)

logger = logging.getLogger(__name__)


@dataclass(frozen=True, slots=True)
class RetryDecision:
    retryable: bool
    uncertain: bool
    wait_seconds: float
    reason: str


def backoff_delay(attempt: int, base: float = 1.0, cap: float = 30.0) -> float:
    raw = min(cap, base * (2 ** max(0, attempt - 1)))
    return raw * (0.7 + random.random() * 0.6)


def classify_exception(exc: BaseException, *, sending: bool = False) -> RetryDecision:
    if isinstance(exc, TelegramRetryAfter):
        return RetryDecision(
            retryable=True,
            uncertain=False,
            wait_seconds=float(max(1, exc.retry_after)),
            reason="TelegramRetryAfter",
        )
    if isinstance(exc, TelegramServerError):
        return RetryDecision(True, False, 2.0, "TelegramServerError")
    if isinstance(exc, TelegramConflictError):
        return RetryDecision(True, False, 5.0, "TelegramConflictError")
    if isinstance(exc, TelegramNetworkError):
        return RetryDecision(True, sending, 2.0, "TelegramNetworkError")
    if isinstance(exc, (asyncio.TimeoutError, TimeoutError)):
        return RetryDecision(True, sending, 2.0, "Timeout")
    if isinstance(
        exc,
        (TelegramBadRequest, TelegramForbiddenError, TelegramUnauthorizedError),
    ):
        return RetryDecision(False, False, 0.0, type(exc).__name__)
    return RetryDecision(False, False, 0.0, type(exc).__name__)


async def sleep_before_retry(attempt: int, decision: RetryDecision, base: float = 1.0) -> None:
    wait = decision.wait_seconds if decision.wait_seconds > 0 else backoff_delay(attempt, base)
    # Respect TelegramRetryAfter but never block an event loop for an unbounded time.
    await asyncio.sleep(min(60.0, wait))
