import random
import time
from typing import Any, Callable, Optional, Sequence, Tuple, Type


def retry_call(
    func: Callable[..., Any],
    *args,
    attempts: int = 3,
    delay_sec: float = 1.0,
    backoff: float = 2.0,
    max_delay_sec: Optional[float] = None,
    jitter_sec: float = 0.0,
    exceptions: Sequence[Type[BaseException]] = (Exception,),
    on_retry: Optional[Callable[[int, BaseException, float], None]] = None,
    sleep_func: Callable[[float], None] = time.sleep,
    **kwargs,
) -> Any:
    if attempts < 1:
        raise ValueError("attempts должно быть >= 1")
    if delay_sec < 0:
        raise ValueError("delay_sec должно быть >= 0")
    if backoff < 1:
        raise ValueError("backoff должно быть >= 1")
    if jitter_sec < 0:
        raise ValueError("jitter_sec должно быть >= 0")
    if max_delay_sec is not None and max_delay_sec < 0:
        raise ValueError("max_delay_sec должно быть >= 0")

    retry_exceptions: Tuple[Type[BaseException], ...] = tuple(exceptions)
    if not retry_exceptions:
        raise ValueError("exceptions не должен быть пустым")

    current_delay = float(delay_sec)
    last_exc: Optional[BaseException] = None

    for attempt in range(1, attempts + 1):
        try:
            return func(*args, **kwargs)
        except retry_exceptions as exc:
            last_exc = exc
            if attempt >= attempts:
                break

            sleep_for = current_delay
            if jitter_sec > 0:
                sleep_for += random.uniform(0, jitter_sec)
            if max_delay_sec is not None:
                sleep_for = min(sleep_for, max_delay_sec)

            if on_retry:
                on_retry(attempt, exc, sleep_for)
            if sleep_for > 0:
                sleep_func(sleep_for)

            next_delay = current_delay * backoff
            if max_delay_sec is not None:
                next_delay = min(next_delay, max_delay_sec)
            current_delay = next_delay

    if last_exc is not None:
        raise last_exc
    raise RuntimeError("retry_call: неизвестная ошибка выполнения")


def retry(
    *,
    attempts: int = 3,
    delay_sec: float = 1.0,
    backoff: float = 2.0,
    max_delay_sec: Optional[float] = None,
    jitter_sec: float = 0.0,
    exceptions: Sequence[Type[BaseException]] = (Exception,),
    on_retry: Optional[Callable[[int, BaseException, float], None]] = None,
    sleep_func: Callable[[float], None] = time.sleep,
):
    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        def wrapped(*args, **kwargs):
            return retry_call(
                func,
                *args,
                attempts=attempts,
                delay_sec=delay_sec,
                backoff=backoff,
                max_delay_sec=max_delay_sec,
                jitter_sec=jitter_sec,
                exceptions=exceptions,
                on_retry=on_retry,
                sleep_func=sleep_func,
                **kwargs,
            )

        wrapped.__name__ = getattr(func, "__name__", "wrapped")
        wrapped.__doc__ = getattr(func, "__doc__")
        return wrapped

    return decorator
