from __future__ import annotations

from typing import Any


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


class APIError(RungateError):
    """HTTP error from the Rungate server."""

    def __init__(
        self,
        message: str,
        *,
        status_code: int,
        body: dict | None = None,
    ) -> None:
        super().__init__(message)
        self.status_code = status_code
        self.body = body
        self.message = message


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


class PermissionDeniedError(APIError):
    """403 — Generic permission denied (non-governance)."""


class NotFoundError(APIError):
    """404 — Resource not found."""


class InternalServerError(APIError):
    """500+ — Server error."""


class ProviderError(APIError):
    """502 — All providers failed. Rungate already retried all of them.

    Attributes:
        provider_errors: List of per-provider failure details.
    """

    def __init__(
        self,
        message: str,
        *,
        status_code: int = 502,
        body: dict | None = None,
        provider_errors: list[dict[str, Any]] | None = None,
    ) -> None:
        super().__init__(message, status_code=status_code, body=body)
        self.provider_errors = provider_errors or []


# ─── Governance Errors ────────────────────────────────────


class GovernanceError(APIError):
    """Base class for all Rungate governance blocks.

    Governance errors are returned when a request violates a policy rule
    (budget, rate limit, model restriction, tool restriction, etc.).

    Attributes:
        governance_code: The specific governance block code (e.g., 'budget_exceeded').
        run_id: The run ID associated with the blocked request (if available).
        details: Additional context about why the request was blocked.
    """

    def __init__(
        self,
        message: str,
        *,
        status_code: int,
        body: dict | None = None,
        governance_code: str,
        run_id: str | None = None,
        details: dict[str, Any] | None = None,
    ) -> None:
        super().__init__(message, status_code=status_code, body=body)
        self.governance_code = governance_code
        self.run_id = run_id
        self.details = details or {}


class BudgetExceededError(GovernanceError):
    """402 — Run budget exceeded (per-run hard limit)."""


class BudgetAlertExceededError(GovernanceError):
    """402 — Budget alert threshold exceeded."""


class HourlyCostExceededError(GovernanceError):
    """402 — Agent hourly cost limit exceeded (per-agent)."""


class RateLimitedError(GovernanceError):
    """429 — Request or token rate limit exceeded."""


class ModelNotAllowedError(GovernanceError):
    """403 — Model is not in allowed_models or is in blocked_models."""


class ToolBlockedError(GovernanceError):
    """403 — Tool is blocked by policy."""


class AgentDisabledError(GovernanceError):
    """403 — Agent has been disabled."""


class RunForceStoppedError(GovernanceError):
    """403 — Run has been force-stopped."""


class RunawayPreventionError(GovernanceError):
    """403 — Run exceeded maximum steps (runaway prevention)."""


class ApprovalRequiredError(GovernanceError):
    """202 — Tools require approval before the request can proceed.

    This is NOT an error in the traditional sense — it's a governance
    state indicating human/agent approval is needed.

    Attributes:
        approval_requests: List of pending approval requests with id and tool name.
    """

    def __init__(
        self,
        message: str,
        *,
        body: dict | None = None,
        run_id: str | None = None,
        approval_requests: list[dict[str, str]] | None = None,
    ) -> None:
        super().__init__(
            message,
            status_code=202,
            body=body,
            governance_code="tool_approval_required",
            run_id=run_id,
            details={},
        )
        self.approval_requests = approval_requests or []


# ─── Connection Errors ────────────────────────────────────


class APIConnectionError(RungateError):
    """Failed to connect to Rungate server."""

    def __init__(self, message: str = "Failed to connect to Rungate server") -> None:
        super().__init__(message)
        self.message = message


class APITimeoutError(RungateError):
    """Request timed out."""

    def __init__(self, message: str = "Request timed out") -> None:
        super().__init__(message)
        self.message = message
