"""Retry decorators for AWS API calls with exponential backoff."""

from __future__ import annotations

from functools import wraps
from typing import TYPE_CHECKING, Any, TypeVar

from botocore.exceptions import ClientError
from tenacity import (
    retry,
    retry_if_exception,
    stop_after_attempt,
    wait_exponential,
)

from alice_cli.console import err_console

if TYPE_CHECKING:
    from collections.abc import Callable

T = TypeVar("T")


def is_retryable_aws_error(exc: BaseException) -> bool:  # noqa: PLR0911
    """Check if an AWS error is transient and should be retried.

    Retryable error codes:
    - ThrottlingException: Rate limit exceeded
    - RequestTimeout: Network timeout
    - ServiceUnavailable / InternalFailure: AWS service issues
    - RequestLimitExceeded: AWS request limit
    - TooManyRequestsException: Rate limiting
    - ProvisionedThroughputExceededException: DynamoDB/Kinesis throttling

    Args:
        exc: Exception to check

    Returns:
        True if the error should be retried, False otherwise
    """
    from alice_cli.errors import GatewayError, QuotaExceededError

    if isinstance(exc, QuotaExceededError | GatewayError):
        return False  # gateway errors are definitive — do not retry

    if not isinstance(exc, ClientError):
        return False

    error_code = exc.response.get("Error", {}).get("Code", "")
    retryable_codes = {
        "ThrottlingException",
        "RequestTimeout",
        "ServiceUnavailable",
        "InternalFailure",
        "RequestLimitExceeded",
        "TooManyRequestsException",
        "ProvisionedThroughputExceededException",
    }
    return error_code in retryable_codes


def with_aws_retry(
    max_attempts: int = 3,
    min_wait: int = 1,
    max_wait: int = 10,
    show_warnings: bool = True,
) -> Callable[[Callable[..., T]], Callable[..., T]]:
    """Decorator to add exponential backoff retry logic to AWS API calls.

    Args:
        max_attempts: Maximum number of attempts (default: 3)
        min_wait: Minimum wait time in seconds (default: 1)
        max_wait: Maximum wait time in seconds (default: 10)
        show_warnings: Print retry warnings to stderr (default: True)

    Returns:
        Decorator function

    Example:
        >>> @with_aws_retry(max_attempts=5)
        >>> def call_bedrock(client, **kwargs):
        ...     return client.converse(**kwargs)
    """

    def decorator(func: Callable[..., T]) -> Callable[..., T]:
        @retry(
            retry=retry_if_exception(is_retryable_aws_error),
            stop=stop_after_attempt(max_attempts),
            wait=wait_exponential(multiplier=1, min=min_wait, max=max_wait),
            reraise=True,
        )
        @wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> T:
            try:
                return func(*args, **kwargs)
            except ClientError as exc:
                if is_retryable_aws_error(exc) and show_warnings:
                    error_code = exc.response.get("Error", {}).get("Code", "Unknown")
                    err_console.print(
                        f"[yellow]⚠ AWS {error_code}, retrying with backoff...[/yellow]"
                    )
                raise

        return wrapper

    return decorator


# Pre-configured decorators for common use cases


def with_bedrock_retry(func: Callable[..., T]) -> Callable[..., T]:
    """Retry Bedrock API calls with default settings.

    Uses 3 attempts with 2-10 second exponential backoff.

    Args:
        func: Function to decorate

    Returns:
        Decorated function with retry logic
    """
    return with_aws_retry(max_attempts=3, min_wait=2, max_wait=10)(func)


def with_s3_retry(func: Callable[..., T]) -> Callable[..., T]:
    """Retry S3 operations with default settings.

    Uses 3 attempts with 1-5 second exponential backoff.

    Args:
        func: Function to decorate

    Returns:
        Decorated function with retry logic
    """
    return with_aws_retry(max_attempts=3, min_wait=1, max_wait=5)(func)


def with_secrets_retry(func: Callable[..., T]) -> Callable[..., T]:
    """Retry Secrets Manager calls with default settings.

    Uses 3 attempts with 1-5 second exponential backoff.

    Args:
        func: Function to decorate

    Returns:
        Decorated function with retry logic
    """
    return with_aws_retry(max_attempts=3, min_wait=1, max_wait=5)(func)
