"""Retry decorator for LLM provider calls.

Provides exponential backoff with jitter for transient failures (429, 500,
502, 503).  Configuration via ``get_settings().albus_llm_max_retries`` and
``get_settings().albus_llm_retry_base_delay``.
"""

from __future__ import annotations

import asyncio
import logging
import random
from functools import wraps
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from collections.abc import Callable, Coroutine

logger = logging.getLogger(__name__)


def with_llm_retry[T](
    fn: Callable[..., Coroutine[Any, Any, T]],
) -> Callable[..., Coroutine[Any, Any, T]]:
    """Wrap an async provider method with exponential backoff + jitter.

    Retries only ``LLMProviderError`` exceptions whose ``is_transient``
    flag is True.  Non-transient errors are raised immediately.

    Reads retry parameters from the global Settings at call time so that
    tests can override them without patching the decorator.
    """

    @wraps(fn)
    async def wrapper(*args: Any, **kwargs: Any) -> T:
        from core.config import get_settings
        from core.llm.providers import LLMProviderError

        settings = get_settings()
        max_retries = settings.albus_llm_max_retries
        base_delay = settings.albus_llm_retry_base_delay

        last_exc: Exception | None = None

        for attempt in range(max_retries + 1):
            try:
                return await fn(*args, **kwargs)
            except LLMProviderError as exc:
                last_exc = exc
                if not exc.is_transient or attempt >= max_retries:
                    raise
                delay = base_delay * (2**attempt) + random.uniform(0, base_delay)  # noqa: S311
                logger.warning(
                    "LLM call failed (attempt %d/%d, retrying in %.1fs): %s",
                    attempt + 1,
                    max_retries + 1,
                    delay,
                    exc,
                )
                await asyncio.sleep(delay)

        # Should not reach here, but satisfy type checker.
        raise last_exc  # type: ignore[misc]

    return wrapper


__all__ = ["with_llm_retry"]
