from __future__ import annotations

import os
from typing import Any

from ._constants import DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, HEADER_API_KEY
from ._exceptions import (
    AgentDisabledError,
    APIError,
    ApprovalRequiredError,
    AuthenticationError,
    BudgetAlertExceededError,
    BudgetExceededError,
    GovernanceError,
    HourlyCostExceededError,
    InternalServerError,
    ModelNotAllowedError,
    NotFoundError,
    PermissionDeniedError,
    ProviderError,
    RateLimitedError,
    RunawayPreventionError,
    RunForceStoppedError,
    RungateError,
    ToolBlockedError,
)
from ._response import RungateResponse
from ._version import __version__

# Governance code → exception class mapping.
# Note: tool_approval_required is NOT in this map — it's handled via the 202 path
# in _handle_response, not through the governance code map. If the server ever returns
# tool_approval_required as a 403 (per governance-error.ts), it falls back to base GovernanceError.
_GOVERNANCE_CODE_MAP: dict[str, type[GovernanceError]] = {
    "budget_exceeded": BudgetExceededError,
    "budget_alert_exceeded": BudgetAlertExceededError,
    "hourly_cost_exceeded": HourlyCostExceededError,
    "rate_limited": RateLimitedError,
    "model_not_allowed": ModelNotAllowedError,
    "tool_blocked": ToolBlockedError,
    "agent_disabled": AgentDisabledError,
    "run_force_stopped": RunForceStoppedError,
    "runaway_prevention": RunawayPreventionError,
}


def _governance_error_for_code(code: str, kwargs: dict[str, Any]) -> GovernanceError:
    """Create the appropriate GovernanceError subclass for a governance block code."""
    cls = _GOVERNANCE_CODE_MAP.get(code, GovernanceError)
    return cls(**kwargs)


class BaseClient:
    """Shared configuration and logic for sync/async clients."""

    def __init__(
        self,
        api_key: str | None = None,
        base_url: str | None = None,
        timeout: float = DEFAULT_TIMEOUT,
        max_retries: int = DEFAULT_MAX_RETRIES,
    ) -> None:
        self.api_key = api_key or os.environ.get("RUNGATE_API_KEY")
        if not self.api_key:
            raise RungateError("API key is required. Pass api_key= or set RUNGATE_API_KEY environment variable.")

        self.base_url = (base_url or os.environ.get("RUNGATE_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
        self.timeout = timeout
        self.max_retries = max_retries

    def _build_headers(self, extra_headers: dict[str, str] | None = None) -> dict[str, str]:
        headers = {
            HEADER_API_KEY: f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "User-Agent": f"rungate-python/{__version__}",
        }
        if extra_headers:
            headers.update(extra_headers)
        return headers

    def _build_url(self, path: str) -> str:
        return f"{self.base_url}{path}"

    def _handle_response(self, response: RungateResponse) -> RungateResponse:
        """Check for errors and raise appropriate exceptions.

        Error detection order:
        1. 202 + awaiting_approval → ApprovalRequiredError
        2. Governance blocks (type: governance_block) → specific GovernanceError subclass
        3. Provider errors (type: provider_error) → ProviderError
        4. Standard HTTP errors → specific exception by status code
        """
        status = response.status_code

        # Special case: 202 approval required
        if status == 202:
            body = response.body
            if isinstance(body, dict) and body.get("status") == "awaiting_approval":
                raise ApprovalRequiredError(
                    message=body.get("message", "Tools require approval"),
                    body=body,
                    run_id=body.get("run_id"),
                    approval_requests=body.get("approval_requests", []),
                )

        # Success
        if 200 <= status < 300:
            return response

        # Parse error body
        body = response.body
        error_data = body.get("error", {}) if isinstance(body, dict) else {}
        message = error_data.get("message", f"HTTP {status}") if isinstance(error_data, dict) else f"HTTP {status}"
        error_type = error_data.get("type", "") if isinstance(error_data, dict) else ""

        # Governance blocks
        if error_type == "governance_block" and isinstance(error_data, dict):
            code = error_data.get("code", "")
            run_id = error_data.get("run_id")
            details = error_data.get("details") if isinstance(error_data.get("details"), dict) else {}
            gov_kwargs: dict[str, Any] = {
                "message": message,
                "status_code": status,
                "body": body,
                "governance_code": code,
                "run_id": run_id,
                "details": details,
            }
            raise _governance_error_for_code(code, gov_kwargs)

        # Provider errors (502)
        if error_type == "provider_error":
            provider_errors = error_data.get("details", []) if isinstance(error_data, dict) else []
            raise ProviderError(
                message=message,
                status_code=status,
                body=body,
                provider_errors=provider_errors if isinstance(provider_errors, list) else [],
            )

        # Standard HTTP errors
        error_kwargs: dict[str, Any] = {"message": message, "status_code": status, "body": body}
        if status == 401:
            raise AuthenticationError(**error_kwargs)
        elif status == 403:
            raise PermissionDeniedError(**error_kwargs)
        elif status == 404:
            raise NotFoundError(**error_kwargs)
        elif status >= 500:
            raise InternalServerError(**error_kwargs)
        else:
            raise APIError(**error_kwargs)
