"""HTTP client with retries, timeouts, and error handling."""

from __future__ import annotations

import asyncio
from typing import Any, Dict, Optional

import httpx

from wallgent.errors import WallgentError, create_error_from_response

DEFAULT_BASE_URL = "https://api.wallgent.dev"
DEFAULT_TIMEOUT = 30.0
MAX_RETRIES = 3
BACKOFF_BASE = 0.1  # seconds


class HttpClient:
    """Sync + async HTTP client for the Wallgent API."""

    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
    ) -> None:
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        }

    # ── Sync request ─────────────────────────────────────────

    def request(
        self,
        method: str,
        path: str,
        body: Optional[Dict[str, Any]] = None,
    ) -> Any:
        url = f"{self._base_url}{path}"
        last_error: Optional[Exception] = None

        for attempt in range(MAX_RETRIES):
            if attempt > 0:
                import time

                time.sleep(BACKOFF_BASE * (4 ** (attempt - 1)))

            try:
                with httpx.Client(timeout=self._timeout) as client:
                    kwargs: Dict[str, Any] = {"headers": self._headers}
                    if body is not None and method not in ("GET", "HEAD"):
                        kwargs["json"] = body

                    resp = client.request(method, url, **kwargs)

                    if resp.status_code >= 500 and attempt < MAX_RETRIES - 1:
                        last_error = WallgentError(
                            "SERVER_ERROR",
                            f"Server error: {resp.status_code}",
                            resp.status_code,
                        )
                        continue

                    return self._handle_response(resp)

            except WallgentError:
                raise
            except httpx.TimeoutException:
                raise WallgentError("TIMEOUT", f"Request timed out after {self._timeout}s", 408)
            except Exception as exc:
                if attempt < MAX_RETRIES - 1:
                    last_error = exc  # type: ignore[assignment]
                    continue
                raise WallgentError("NETWORK_ERROR", str(exc), 0)

        raise last_error or WallgentError("UNKNOWN_ERROR", "Request failed", 0)

    # ── Async request ────────────────────────────────────────

    async def arequest(
        self,
        method: str,
        path: str,
        body: Optional[Dict[str, Any]] = None,
    ) -> Any:
        url = f"{self._base_url}{path}"
        last_error: Optional[Exception] = None

        for attempt in range(MAX_RETRIES):
            if attempt > 0:
                await asyncio.sleep(BACKOFF_BASE * (4 ** (attempt - 1)))

            try:
                async with httpx.AsyncClient(timeout=self._timeout) as client:
                    kwargs: Dict[str, Any] = {"headers": self._headers}
                    if body is not None and method not in ("GET", "HEAD"):
                        kwargs["json"] = body

                    resp = await client.request(method, url, **kwargs)

                    if resp.status_code >= 500 and attempt < MAX_RETRIES - 1:
                        last_error = WallgentError(
                            "SERVER_ERROR",
                            f"Server error: {resp.status_code}",
                            resp.status_code,
                        )
                        continue

                    return self._handle_response(resp)

            except WallgentError:
                raise
            except httpx.TimeoutException:
                raise WallgentError("TIMEOUT", f"Request timed out after {self._timeout}s", 408)
            except Exception as exc:
                if attempt < MAX_RETRIES - 1:
                    last_error = exc  # type: ignore[assignment]
                    continue
                raise WallgentError("NETWORK_ERROR", str(exc), 0)

        raise last_error or WallgentError("UNKNOWN_ERROR", "Request failed", 0)

    # ── Shared response handling ─────────────────────────────

    @staticmethod
    def _handle_response(resp: httpx.Response) -> Any:
        if resp.status_code == 204:
            return None

        try:
            data = resp.json()
        except Exception:
            data = None

        if not resp.is_success:
            if isinstance(data, dict) and "error" in data:
                raise create_error_from_response(resp.status_code, data)
            raise WallgentError(
                "API_ERROR",
                f"API request failed with status {resp.status_code}",
                resp.status_code,
            )

        return data
