"""RunBlob SDK exceptions."""
from __future__ import annotations

from typing import Any

import httpx


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

    message: str

    def __init__(self, message: str) -> None:
        super().__init__(message)
        self.message = message


class APIError(RunBlobError):
    """Error returned by the RunBlob API."""

    response: httpx.Response
    status_code: int
    request_id: str | None
    body: Any

    def __init__(
        self,
        message: str,
        *,
        response: httpx.Response,
        status_code: int | None = None,
        body: Any = None,
    ) -> None:
        self.response = response
        self.status_code = status_code or response.status_code
        self.request_id = response.headers.get("X-Request-ID")
        self.body = body
        detail = f"[{self.status_code}] {message}"
        if self.request_id:
            detail += f" (request_id: {self.request_id})"
        super().__init__(detail)


class APIStatusError(APIError):
    """Non-2xx HTTP response (generic)."""


class AuthenticationError(APIError):
    """401 Unauthorized — invalid or missing API key."""

    def __init__(self, message: str, *, response: httpx.Response, body: Any = None) -> None:
        super().__init__(message, response=response, status_code=401, body=body)


class InsufficientCreditsError(APIError):
    """402 Payment Required — not enough balance."""

    def __init__(self, message: str, *, response: httpx.Response, body: Any = None) -> None:
        super().__init__(message, response=response, status_code=402, body=body)


class PermissionDeniedError(APIError):
    """403 Forbidden."""

    def __init__(self, message: str, *, response: httpx.Response, body: Any = None) -> None:
        super().__init__(message, response=response, status_code=403, body=body)


class NotFoundError(APIError):
    """404 Not Found."""

    def __init__(self, message: str, *, response: httpx.Response, body: Any = None) -> None:
        super().__init__(message, response=response, status_code=404, body=body)


class RateLimitError(APIError):
    """429 Too Many Requests."""

    retry_after: float | None

    def __init__(
        self,
        message: str,
        *,
        response: httpx.Response,
        body: Any = None,
        retry_after: float | None = None,
    ) -> None:
        self.retry_after = retry_after
        super().__init__(message, response=response, status_code=429, body=body)


class InternalServerError(APIError):
    """5xx Server Error."""


class APIConnectionError(RunBlobError):
    """Network-level connection failure (DNS, TCP, TLS)."""


class APITimeoutError(APIConnectionError):
    """Request exceeded the configured timeout."""


class CircuitBreakerOpenError(RunBlobError):
    """Circuit breaker is open — requests are being rejected to protect the system."""


class GenerationTimeoutError(RunBlobError):
    """Polling for generation result exceeded the configured timeout."""

    task_uuid: str

    def __init__(self, task_uuid: str, timeout: float) -> None:
        self.task_uuid = task_uuid
        super().__init__(
            f"Generation {task_uuid} did not complete within {timeout:.0f}s"
        )


_STATUS_MAP: dict[int, type[APIError]] = {
    401: AuthenticationError,
    402: InsufficientCreditsError,
    403: PermissionDeniedError,
    404: NotFoundError,
    429: RateLimitError,
    500: InternalServerError,
    502: InternalServerError,
    503: InternalServerError,
    504: InternalServerError,
}


def raise_for_status(response: httpx.Response) -> None:
    """Raise the appropriate RunBlob exception for non-2xx responses."""
    if response.is_success:
        return

    body: Any = None
    message = response.text
    try:
        body = response.json()
        if isinstance(body, dict):
            message = body.get("error", body.get("message", message))
    except Exception:
        pass

    exc_cls = _STATUS_MAP.get(response.status_code, APIStatusError)

    kwargs: dict[str, Any] = {"message": message, "response": response, "body": body}

    if exc_cls is RateLimitError:
        retry_after_raw = response.headers.get("Retry-After")
        retry_after: float | None = None
        if retry_after_raw:
            try:
                retry_after = float(retry_after_raw)
            except ValueError:
                pass
        kwargs["retry_after"] = retry_after

    raise exc_cls(**kwargs)
