"""
Exception hierarchy mirroring the server error codes.
"""

from __future__ import annotations

from typing import Any


class MachinsError(Exception):
    """Base exception for all SDK errors."""

    def __init__(
        self,
        message: str,
        *,
        code: str = "UNKNOWN",
        status_code: int = 0,
        details: dict[str, Any] | None = None,
    ):
        self.code = code
        self.message = message
        self.status_code = status_code
        self.details = details or {}
        super().__init__(message)


class NotFoundError(MachinsError):
    pass


class AuthenticationError(MachinsError):
    pass


class ForbiddenError(MachinsError):
    pass


class InsufficientFundsError(MachinsError):
    pass


class InvalidTransitionError(MachinsError):
    pass


class DuplicateError(MachinsError):
    pass


class RateLimitedError(MachinsError):
    pass


class NetworkError(MachinsError):
    pass


# Maps server error codes → SDK exception classes
_CODE_MAP: dict[str, type[MachinsError]] = {
    "NOT_FOUND": NotFoundError,
    "UNAUTHORIZED": AuthenticationError,
    "FORBIDDEN": ForbiddenError,
    "INSUFFICIENT_FUNDS": InsufficientFundsError,
    "INVALID_TRANSITION": InvalidTransitionError,
    "DUPLICATE": DuplicateError,
    "RATE_LIMITED": RateLimitedError,
    "CONCURRENCY_CONFLICT": MachinsError,
    "IDEMPOTENCY_CONFLICT": MachinsError,
}


def raise_for_status(response) -> None:  # noqa: ANN001 — httpx.Response
    """Parse server error JSON and raise a typed exception."""
    if response.status_code < 400:
        return

    try:
        body = response.json()
        error = body.get("error", body.get("detail", {}))
        if isinstance(error, dict):
            code = error.get("code", "UNKNOWN")
            message = error.get("message", response.text)
            details = error.get("details", {})
        else:
            code = "UNKNOWN"
            message = str(error)
            details = {}
    except Exception:
        code = "UNKNOWN"
        message = response.text or f"HTTP {response.status_code}"
        details = {}

    exc_cls = _CODE_MAP.get(code, MachinsError)
    raise exc_cls(message, code=code, status_code=response.status_code, details=details)
