from __future__ import annotations

import math
from datetime import datetime, timezone
from typing import Any, Dict, Optional


class GunSpecError(Exception):
    """Base error class for every error raised by the GunSpec SDK."""


class APIError(GunSpecError):
    """An error returned by the GunSpec API with an HTTP status code."""

    status: int
    code: str
    message: str
    request_id: str
    headers: Dict[str, str]

    def __init__(
        self,
        status: int,
        code: str,
        message: str,
        request_id: str,
        headers: Dict[str, str],
    ) -> None:
        super().__init__(message)
        self.status = status
        self.code = code
        self.message = message
        self.request_id = request_id
        self.headers = headers


class AuthenticationError(APIError):
    """Raised when the API returns 401 Unauthorized."""

    def __init__(
        self,
        code: str,
        message: str,
        request_id: str,
        headers: Dict[str, str],
    ) -> None:
        super().__init__(401, code, message, request_id, headers)


class PermissionDeniedError(APIError):
    """Raised when the API returns 403 Forbidden."""

    def __init__(
        self,
        code: str,
        message: str,
        request_id: str,
        headers: Dict[str, str],
    ) -> None:
        super().__init__(403, code, message, request_id, headers)


class NotFoundError(APIError):
    """Raised when the API returns 404 Not Found."""

    def __init__(
        self,
        code: str,
        message: str,
        request_id: str,
        headers: Dict[str, str],
    ) -> None:
        super().__init__(404, code, message, request_id, headers)


class BadRequestError(APIError):
    """Raised when the API returns 400 Bad Request."""

    def __init__(
        self,
        code: str,
        message: str,
        request_id: str,
        headers: Dict[str, str],
    ) -> None:
        super().__init__(400, code, message, request_id, headers)


class RateLimitError(APIError):
    """Raised when the API returns 429 Too Many Requests."""

    retry_after: Optional[float]

    def __init__(
        self,
        code: str,
        message: str,
        request_id: str,
        headers: Dict[str, str],
        retry_after: Optional[float],
    ) -> None:
        super().__init__(429, code, message, request_id, headers)
        self.retry_after = retry_after


class InternalServerError(APIError):
    """Raised when the API returns 500 Internal Server Error."""

    def __init__(
        self,
        code: str,
        message: str,
        request_id: str,
        headers: Dict[str, str],
    ) -> None:
        super().__init__(500, code, message, request_id, headers)


class ConnectError(GunSpecError):
    """Raised on network-level failures (DNS, connection reset, etc.)."""

    cause: Optional[BaseException]

    def __init__(self, message: str, cause: Optional[BaseException] = None) -> None:
        super().__init__(message)
        self.cause = cause


class RequestTimeoutError(GunSpecError):
    """Raised when a request exceeds the configured timeout."""

    timeout_s: float

    def __init__(self, timeout_s: float) -> None:
        super().__init__(f"Request timed out after {timeout_s}s")
        self.timeout_s = timeout_s


def _parse_retry_after(headers: Dict[str, str]) -> Optional[float]:
    """Parse the Retry-After header value into seconds."""
    raw = headers.get("Retry-After") or headers.get("retry-after")
    if raw is None:
        return None

    # Try delta-seconds first.
    try:
        seconds = float(raw)
        if not math.isnan(seconds) and seconds >= 0:
            return seconds
    except ValueError:
        pass

    # Fall back to HTTP-date.
    try:
        dt = datetime.strptime(raw, "%a, %d %b %Y %H:%M:%S %Z")
        dt = dt.replace(tzinfo=timezone.utc)
        delta = (dt - datetime.now(timezone.utc)).total_seconds()
        return max(delta, 0.0)
    except ValueError:
        pass

    return None


def create_api_error(
    status: int,
    body: Optional[Dict[str, Any]],
    request_id: str,
    headers: Dict[str, str],
) -> APIError:
    """Create the appropriate APIError subclass for the given HTTP status."""
    error_data = body.get("error", {}) if body else {}
    code: str = error_data.get("code", f"HTTP_{status}")
    message: str = error_data.get("message", f"Request failed with status {status}")

    if status == 400:
        return BadRequestError(code, message, request_id, headers)
    if status == 401:
        return AuthenticationError(code, message, request_id, headers)
    if status == 403:
        return PermissionDeniedError(code, message, request_id, headers)
    if status == 404:
        return NotFoundError(code, message, request_id, headers)
    if status == 429:
        return RateLimitError(
            code, message, request_id, headers, _parse_retry_after(headers)
        )
    if status == 500:
        return InternalServerError(code, message, request_id, headers)
    return APIError(status, code, message, request_id, headers)
