"""Hierarquia de exceções da abacapython.

Todas as exceções derivam de :class:`AbacaPayError`, permitindo capturar
qualquer erro originado pelo SDK com um único `except`.

Exemplos:
    >>> from abacapython import AbacaPay
    >>> from abacapython.exceptions import AuthenticationError, RateLimitError
    >>> client = AbacaPay(api_key="invalida")
    >>> try:
    ...     client.store.get()
    ... except AuthenticationError:
    ...     print("API key inválida")
"""

from __future__ import annotations

from collections.abc import Mapping
from typing import Any


class AbacaPayError(Exception):
    """Erro base de todos os erros lançados pela abacapython."""

    def __init__(
        self,
        message: str,
        *,
        status_code: int | None = None,
        response_body: Any | None = None,
        request_id: str | None = None,
    ) -> None:
        super().__init__(message)
        self.message = message
        self.status_code = status_code
        self.response_body = response_body
        self.request_id = request_id

    def __repr__(self) -> str:  # pragma: no cover - cosmético
        return (
            f"{self.__class__.__name__}(message={self.message!r}, "
            f"status_code={self.status_code!r}, request_id={self.request_id!r})"
        )


class APIConnectionError(AbacaPayError):
    """Falha ao conectar com a API (DNS, timeout, conexão recusada, etc.)."""


class APITimeoutError(APIConnectionError):
    """Timeout esperando resposta da API."""


class APIResponseError(AbacaPayError):
    """Erro genérico retornado pela API (status >= 400)."""


class BadRequestError(APIResponseError):
    """HTTP 400 — requisição inválida (campos faltando, formato inválido, etc.)."""


class AuthenticationError(APIResponseError):
    """HTTP 401 — API key ausente, inválida ou revogada."""


class PermissionDeniedError(APIResponseError):
    """HTTP 403 — credencial válida mas sem permissão para o recurso."""


class NotFoundError(APIResponseError):
    """HTTP 404 — recurso não encontrado."""


class ConflictError(APIResponseError):
    """HTTP 409 — conflito de estado (ex.: cupom já existente)."""


class UnprocessableEntityError(APIResponseError):
    """HTTP 422 — payload válido em forma mas inválido em conteúdo."""


class RateLimitError(APIResponseError):
    """HTTP 429 — limite de requisições excedido."""


class ServerError(APIResponseError):
    """HTTP 5xx — erro do lado do servidor."""


_STATUS_TO_EXCEPTION: Mapping[int, type[APIResponseError]] = {
    400: BadRequestError,
    401: AuthenticationError,
    403: PermissionDeniedError,
    404: NotFoundError,
    409: ConflictError,
    422: UnprocessableEntityError,
    429: RateLimitError,
}


def exception_for_status(status_code: int) -> type[APIResponseError]:
    """Mapeia um status HTTP para a subclasse apropriada de :class:`APIResponseError`.

    Códigos não mapeados explicitamente recaem em :class:`ServerError` (>=500)
    ou :class:`APIResponseError` (demais 4xx).
    """
    if status_code in _STATUS_TO_EXCEPTION:
        return _STATUS_TO_EXCEPTION[status_code]
    if status_code >= 500:
        return ServerError
    return APIResponseError


class WebhookSignatureError(AbacaPayError):
    """Assinatura HMAC do webhook não bate com o payload."""


__all__ = [
    "APIConnectionError",
    "APIResponseError",
    "APITimeoutError",
    "AbacaPayError",
    "AuthenticationError",
    "BadRequestError",
    "ConflictError",
    "NotFoundError",
    "PermissionDeniedError",
    "RateLimitError",
    "ServerError",
    "UnprocessableEntityError",
    "WebhookSignatureError",
    "exception_for_status",
]
