from __future__ import annotations

import os
import time
from typing import Any

import httpx

from gamma23._utils import deep_camel_to_snake, deep_snake_to_camel
from gamma23.errors import (
    AuthenticationError,
    Gamma23Error,
    NotFoundError,
    RateLimitError,
    ValidationError,
)

_DEFAULT_BASE_URL = "https://gamma23.app"
_RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}


class Gamma23:
    """Official Python client for the Gamma23 API.

    Args:
        api_key: Your Gamma23 API key (``gm23_...``).  Falls back to the
            ``GAMMA23_API_KEY`` environment variable when *None*.
        base_url: Override the API base URL.  Defaults to the
            ``GAMMA23_API_URL`` environment variable or
            ``https://gamma23.app``.
        timeout: Request timeout in seconds.
        max_retries: Maximum number of retries for transient errors.
    """

    def __init__(
        self,
        api_key: str | None = None,
        *,
        base_url: str | None = None,
        timeout: float = 30.0,
        max_retries: int = 3,
    ) -> None:
        self.api_key = api_key or os.environ.get("GAMMA23_API_KEY", "")
        if not self.api_key:
            raise Gamma23Error(
                "No API key provided. Pass api_key or set the GAMMA23_API_KEY "
                "environment variable."
            )

        self.base_url = (
            base_url
            or os.environ.get("GAMMA23_API_URL")
            or _DEFAULT_BASE_URL
        ).rstrip("/")

        self.timeout = timeout
        self.max_retries = max_retries

        self._client = httpx.Client(
            base_url=self.base_url,
            timeout=httpx.Timeout(timeout),
            headers={
                "Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json",
                "User-Agent": "gamma23-python/0.1.0",
            },
        )

        # Lazy imports to avoid circular dependencies
        from gamma23.resources.agents import Agents
        from gamma23.resources.payments import Payments
        from gamma23.resources.spend_intents import SpendIntents

        self.spend_intents = SpendIntents(self)
        self.payments = Payments(self)
        self.agents = Agents(self)

    def _request(
        self,
        method: str,
        path: str,
        *,
        json: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None,
        headers: dict[str, str] | None = None,
    ) -> Any:
        """Send an HTTP request to the Gamma23 API.

        Handles retries with exponential backoff for transient failures
        (HTTP 429 and 5xx) and converts response keys from camelCase to
        snake_case.
        """
        if json is not None:
            json = deep_snake_to_camel(json)

        extra_headers = headers or {}
        last_exc: Exception | None = None

        for attempt in range(self.max_retries + 1):
            try:
                response = self._client.request(
                    method,
                    path,
                    json=json,
                    params=params,
                    headers=extra_headers,
                )
            except httpx.TimeoutException as exc:
                last_exc = exc
                if attempt < self.max_retries:
                    self._backoff(attempt)
                    continue
                raise Gamma23Error(
                    f"Request timed out after {self.timeout}s"
                ) from exc
            except httpx.HTTPError as exc:
                last_exc = exc
                if attempt < self.max_retries:
                    self._backoff(attempt)
                    continue
                raise Gamma23Error(
                    f"HTTP transport error: {exc}"
                ) from exc

            if response.status_code < 300:
                if response.status_code == 204:
                    return None
                return deep_camel_to_snake(response.json())

            if (
                response.status_code in _RETRYABLE_STATUS_CODES
                and attempt < self.max_retries
            ):
                retry_after = response.headers.get("Retry-After")
                if retry_after:
                    time.sleep(float(retry_after))
                else:
                    self._backoff(attempt)
                continue

            self._raise_for_status(response)

        raise Gamma23Error(f"Max retries ({self.max_retries}) exceeded") from last_exc

    @staticmethod
    def _backoff(attempt: int) -> None:
        delay = min(2 ** attempt * 0.5, 8.0)
        time.sleep(delay)

    @staticmethod
    def _raise_for_status(response: httpx.Response) -> None:
        request_id = response.headers.get("x-request-id")

        try:
            body = response.json()
        except Exception:
            body = {}

        message = body.get("message") or body.get("error") or response.text
        code = body.get("code")

        error_cls: type[Gamma23Error]
        if response.status_code == 401:
            error_cls = AuthenticationError
        elif response.status_code == 404:
            error_cls = NotFoundError
        elif response.status_code == 422:
            error_cls = ValidationError
        elif response.status_code == 429:
            error_cls = RateLimitError
        else:
            error_cls = Gamma23Error

        raise error_cls(
            message,
            status_code=response.status_code,
            code=code,
            request_id=request_id,
        )

    def close(self) -> None:
        """Close the underlying HTTP client."""
        self._client.close()

    def __enter__(self) -> Gamma23:
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()
