"""Typed error hierarchy for ``gate``.

Mirrors the Node SDK's ``@0gate/gate-node`` error classes one-to-one so
partners running both stacks recognise the surface. Always raises a
subclass — never the base ``GateError`` directly.

Callers can ``except`` by concrete class or branch on ``.code``.
"""

from __future__ import annotations

from typing import Any, Literal, Optional, Union

ErrorCode = Literal[
    "authentication_error",
    "permission_error",
    "invalid_request",
    "idempotency_error",
    "rate_limit",
    "api_error",
    "connection_error",
    "timeout",
    "unknown",
]


class GateError(Exception):
    """Base for every error this SDK throws."""

    code: ErrorCode = "unknown"

    def __init__(
        self,
        message: str,
        *,
        status_code: Optional[int] = None,
        request_id: Optional[str] = None,
        raw: Any = None,
    ) -> None:
        super().__init__(message)
        self.status_code = status_code
        self.request_id = request_id
        self.raw = raw

    def __repr__(self) -> str:  # pragma: no cover — for debugging only
        return (
            f"{type(self).__name__}(message={self.args[0]!r}, "
            f"status_code={self.status_code}, request_id={self.request_id})"
        )


class AuthenticationError(GateError):
    code: ErrorCode = "authentication_error"


class PermissionError(GateError):  # noqa: A001 — shadowing builtin is intentional, namespaced
    code: ErrorCode = "permission_error"


class InvalidRequestError(GateError):
    code: ErrorCode = "invalid_request"


class IdempotencyError(GateError):
    code: ErrorCode = "idempotency_error"


class RateLimitError(GateError):
    code: ErrorCode = "rate_limit"

    def __init__(
        self,
        message: str,
        *,
        retry_after_seconds: Optional[float] = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(message, **kwargs)
        self.retry_after_seconds = retry_after_seconds


class APIError(GateError):
    code: ErrorCode = "api_error"


class APIConnectionError(GateError):
    code: ErrorCode = "connection_error"


class TimeoutError(GateError):  # noqa: A001 — namespaced
    code: ErrorCode = "timeout"


class WebhookSignatureError(GateError):
    code: ErrorCode = "invalid_request"


def build_error_from_response(
    status: int,
    body: Any,
    request_id: Optional[str],
) -> GateError:
    """Map an HTTP response to the right concrete subclass."""
    message = _extract_message(body) or f"HTTP {status}"

    cls: type[GateError]
    if status in (400, 422):
        cls = InvalidRequestError
    elif status == 401:
        cls = AuthenticationError
    elif status == 403:
        cls = PermissionError
    elif status == 409:
        cls = IdempotencyError
    elif status == 429:
        cls = RateLimitError
    elif status >= 500:
        cls = APIError
    else:
        return GateError(message, status_code=status, request_id=request_id, raw=body)

    return cls(message, status_code=status, request_id=request_id, raw=body)


def _extract_message(body: Any) -> Optional[str]:
    if not isinstance(body, dict):
        return None
    message = body.get("message")
    if isinstance(message, str):
        return message
    if isinstance(message, list):
        return "; ".join(str(m) for m in message if isinstance(m, str))
    return None


__all__ = [
    "ErrorCode",
    "GateError",
    "AuthenticationError",
    "PermissionError",
    "InvalidRequestError",
    "IdempotencyError",
    "RateLimitError",
    "APIError",
    "APIConnectionError",
    "TimeoutError",
    "WebhookSignatureError",
    "build_error_from_response",
]
