import asyncio
import random
import time
from collections.abc import Callable
from functools import wraps

DEFAULT_RETRIES = 3
DEFAULT_DELAY = 0.5
DEFAULT_BACKOFF = 2.0


def _next_delay(current: float, backoff: float) -> float:
    """Calculate the delay before the next attempt."""
    return current * backoff + random.uniform(0, 0.1)


def with_retry(
    *,
    retries: int = DEFAULT_RETRIES,
    delay: float = DEFAULT_DELAY,
    backoff: float = DEFAULT_BACKOFF,
    exceptions: tuple[type[Exception], ...] = (Exception,),
) -> Callable:
    """Retry decorator with exponential backoff and jitter. Supports both sync and async functions.

    Args:
        retries: Maximum number of retries before raising.
        delay: Initial delay (seconds) between attempts.
        backoff: Backoff multiplier per attempt.
        exceptions: Tuple of Exception types which will trigger a retry.
    """

    def decorator(func: Callable) -> Callable:
        if asyncio.iscoroutinefunction(func):

            @wraps(func)
            async def async_wrapper(*args, **kwargs):
                current_delay = delay
                for attempt in range(retries + 1):
                    try:
                        return await func(*args, **kwargs)
                    except exceptions:
                        if attempt == retries:
                            raise
                        await asyncio.sleep(current_delay)
                        current_delay = _next_delay(current_delay, backoff)

            return async_wrapper

        else:

            @wraps(func)
            def sync_wrapper(*args, **kwargs):
                current_delay = delay
                for attempt in range(retries + 1):
                    try:
                        return func(*args, **kwargs)
                    except exceptions:
                        if attempt == retries:
                            raise
                        time.sleep(current_delay)
                        current_delay = _next_delay(current_delay, backoff)

            return sync_wrapper

    return decorator
