"""
— SDK Exception Hierarchy
=====================================================
All SDK exceptions inherit from NEESError so callers can catch
the base class or be specific about which failure they handle.

Hierarchy:
  NEESError
    ├── AuthError          — 401 Unauthorized (bad or missing API key)
    ├── TimeoutError       — Request exceeded the configured timeout
    ├── RateLimitError     — 429 Too Many Requests
    ├── GovernanceError    — 503 pipeline not initialised / halted
    ├── ValidationError    — 422 bad request payload (caller bug)
    └── NEESAPIError       — Any other non-2xx HTTP response
"""

from __future__ import annotations


class NEESError(Exception):
    """Base class for all NEES GP SDK exceptions."""


class AuthError(NEESError):
    """
    Raised when the API returns HTTP 401.
    Fix: check that api_key is correct and has not been revoked.
    """

    def __init__(self, message: str = "Invalid or missing API key.") -> None:
        super().__init__(message)


class TimeoutError(NEESError):
    """
    Raised when the HTTP request exceeds the configured timeout.
    Fix: increase Client(timeout=...) or check server health.
    """

    def __init__(self, url: str, timeout_s: float) -> None:
        super().__init__(
            f"Request to {url} timed out after {timeout_s}s. "
            "Increase Client(timeout=...) or check server health."
        )
        self.url       = url
        self.timeout_s = timeout_s


class RateLimitError(NEESError):
    """
    Raised when the API returns HTTP 429.
    The retry_after_seconds attribute is set if the server provides it.
    """

    def __init__(self, retry_after_seconds: float = 0.0) -> None:
        super().__init__(
            f"Rate limit exceeded. "
            + (f"Retry after {retry_after_seconds}s." if retry_after_seconds else "Try again later.")
        )
        self.retry_after_seconds = retry_after_seconds


class GovernanceError(NEESError):
    """
    Raised when the NEES GP governance pipeline is unavailable (503).
    This typically means the server is still initialising or has halted.
    """

    def __init__(self, detail: str = "Governance pipeline unavailable.") -> None:
        super().__init__(detail)
        self.detail = detail


class ValidationError(NEESError):
    """
    Raised when the server returns HTTP 422 (Unprocessable Entity).
    This indicates a bug in the caller — the request payload is invalid.
    """

    def __init__(self, errors: list | None = None) -> None:
        msg = "Request validation failed."
        if errors:
            msg += f" Errors: {errors}"
        super().__init__(msg)
        self.errors = errors or []


class NEESAPIError(NEESError):
    """
    Raised for any other non-2xx HTTP response not covered by a specific subclass.
    Carries the raw status_code and response body for debugging.
    """

    def __init__(self, status_code: int, body: str) -> None:
        super().__init__(
            f"NEES GP API error {status_code}: {body[:200]}"
        )
        self.status_code = status_code
        self.body        = body
