from __future__ import annotations
import asyncio, random
from functools import wraps
from typing import Callable, Awaitable, TypeVar

T = TypeVar("T")

def async_retry(attempts: int = 3, base_delay_s: float = 0.7, max_delay_s: float = 8.0):
    def deco(fn: Callable[..., Awaitable[T]]):
        @wraps(fn)
        async def wrapper(*args, **kwargs) -> T:
            last = None
            for i in range(attempts):
                try:
                    return await fn(*args, **kwargs)
                except Exception as e:
                    last = e
                    delay = min(max_delay_s, base_delay_s * (2 ** i)) * (0.7 + random.random() * 0.6)
                    await asyncio.sleep(delay)
            raise last  # type: ignore
        return wrapper
    return deco
