"""Retry wrappers and exception hierarchy."""
from __future__ import annotations

import functools
from collections.abc import Callable
from typing import Any, TypeVar

from tenacity import (
    RetryError,
    retry,
    retry_if_exception_type,
    stop_after_attempt,
    wait_exponential,
)

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


class ACEFError(Exception):
    """Base error for ACE-F."""

    def __init__(
        self,
        message: str,
        source: str = "",
        entity: str = "",
        context: dict[str, Any] | None = None,
    ) -> None:
        super().__init__(message)
        self.source = source
        self.entity = entity
        self.context = context or {}


class RetryableError(ACEFError):
    """Transient error that can be retried (network, timeout, 5xx)."""


class NonRetryableError(ACEFError):
    """Permanent error that must not be retried (404, business rule)."""


class ValidationError(ACEFError):
    """Schema or data validation failure — never retried."""


class DependencyError(ACEFError):
    """Required upstream dependency unavailable."""


def with_retry(
    max_attempts: int = 3,
    min_wait: float = 1.0,
    max_wait: float = 30.0,
    multiplier: float = 2.0,
) -> Callable[[F], F]:
    """Decorator: retry on RetryableError with exponential backoff."""

    def decorator(func: F) -> F:
        @retry(
            retry=retry_if_exception_type(RetryableError),
            stop=stop_after_attempt(max_attempts),
            wait=wait_exponential(multiplier=multiplier, min=min_wait, max=max_wait),
            reraise=True,
        )
        @functools.wraps(func)
        async def wrapper(*args: Any, **kwargs: Any) -> Any:
            return await func(*args, **kwargs)

        return wrapper  # type: ignore[return-value]

    return decorator


__all__ = [
    "ACEFError",
    "RetryableError",
    "NonRetryableError",
    "ValidationError",
    "DependencyError",
    "with_retry",
    "RetryError",
]
