"""Retry decorators for transient failures.

Provides two pre-configured decorators:

* :data:`DEFAULT_RETRY` — 3 attempts with exponential back-off (1 s / 2 s / 4 s),
  triggered by :class:`~dexfather.exceptions.NetworkError` and
  :class:`~dexfather.exceptions.RateLimited`.  When retrying a
  :class:`RateLimited` exception the decorator respects the server's
  ``retry_after`` value instead of the generic backoff.

* :data:`NO_RETRY` — identity decorator (pass-through) for operations
  that must never be retried (e.g. non-idempotent writes the caller
  explicitly opts out of retrying).

If *tenacity* is not installed both decorators degrade to identity
functions so that the rest of the SDK continues to work.
"""

from __future__ import annotations

import warnings
from typing import Any, Callable, TypeVar

from .exceptions import NetworkError, RateLimited, MAX_RETRY_AFTER

F = TypeVar("F", bound=Callable[..., Any])

try:
    from tenacity import (
        retry,
        retry_if_exception_type,
        stop_after_attempt,
        wait_exponential,
    )

    _HAS_TENACITY = True
except ImportError:  # pragma: no cover
    _HAS_TENACITY = False


def _identity_decorator(fn: F) -> F:
    """No-op decorator that returns the function unchanged."""
    return fn


def _wait_for_rate_limit(retry_state: Any) -> float:
    """Custom wait strategy that respects RateLimited.retry_after.

    When the exception is :class:`RateLimited` and carries a positive
    ``retry_after`` value, that value is used (capped at
    :data:`MAX_RETRY_AFTER`).  Otherwise the standard exponential
    backoff (1 s / 2 s / 4 s) is applied.
    """
    exc = retry_state.outcome.exception()
    if isinstance(exc, RateLimited) and exc.retry_after > 0:
        return min(exc.retry_after, MAX_RETRY_AFTER)
    return wait_exponential(multiplier=1, min=1, max=4)(retry_state)


if _HAS_TENACITY:
    DEFAULT_RETRY = retry(
        stop=stop_after_attempt(3),
        wait=_wait_for_rate_limit,
        retry=retry_if_exception_type((NetworkError, RateLimited)),
        reraise=True,
    )
else:  # pragma: no cover
    warnings.warn(
        "tenacity is not installed — retry decorators are disabled. "
        "Install it with: pip install tenacity",
        stacklevel=2,
    )
    DEFAULT_RETRY = _identity_decorator  # type: ignore[assignment]

NO_RETRY: Callable[[F], F] = _identity_decorator
