"""Low-level HTTP client with retry, polling, and auth."""

from __future__ import annotations

import time
from typing import Any, Callable

import requests

from .errors import (
    AuthenticationError,
    InsufficientBalanceError,
    GTMRouterError,
    PollTimeoutError,
    RateLimitError,
    ValidationError,
)

_TERMINAL_STATUSES = {"completed", "failed", "partial", "cancelled"}


class HttpClient:
    def __init__(
        self,
        api_key: str,
        base_url: str,
        timeout: int,
        max_retries: int,
        retry_delay: float,
        poll_interval: float,
        poll_timeout: float,
    ):
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._max_retries = max_retries
        self._retry_delay = retry_delay
        self.poll_interval = poll_interval
        self.poll_timeout = poll_timeout
        self._session = requests.Session()
        self._session.headers.update(
            {
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "User-Agent": "gtm-router-python/1.0.0",
            }
        )

    def request(
        self,
        method: str,
        path: str,
        json_body: dict | None = None,
        params: dict | None = None,
    ) -> dict[str, Any]:
        url = f"{self._base_url}{path}"
        last_error: Exception | None = None

        for attempt in range(1 + self._max_retries):
            try:
                resp = self._session.request(
                    method,
                    url,
                    json=json_body,
                    params=params,
                    timeout=self._timeout,
                )
                if resp.status_code >= 500 and attempt < self._max_retries:
                    time.sleep(self._retry_delay * (2**attempt))
                    continue
                return self._handle_response(resp)
            except requests.ConnectionError as exc:
                last_error = exc
                if attempt < self._max_retries:
                    time.sleep(self._retry_delay * (2**attempt))
                    continue

        raise GTMRouterError(f"Request failed after {self._max_retries + 1} attempts: {last_error}")

    def poll(
        self,
        path: str,
        on_progress: Callable[[dict], None] | None = None,
    ) -> dict[str, Any]:
        start = time.monotonic()
        while True:
            data = self.request("GET", path)
            status = data.get("status", "")

            if on_progress:
                on_progress(data)

            if status in _TERMINAL_STATUSES:
                return data

            elapsed = time.monotonic() - start
            if elapsed >= self.poll_timeout:
                job_id = data.get("id", path.split("/")[-1])
                raise PollTimeoutError(job_id, elapsed)

            time.sleep(self.poll_interval)

    def fetch_all_results(self, job_id: str, page_size: int = 100) -> list[dict]:
        results: list[dict] = []
        offset = 0
        while True:
            data = self.request(
                "GET",
                f"/bulk/{job_id}/results",
                params={"limit": page_size, "offset": offset},
            )
            page = data.get("data", [])
            if not page:
                break
            results.extend(page)
            offset += page_size
            if len(page) < page_size:
                break
        return results

    @staticmethod
    def _handle_response(resp: requests.Response) -> dict[str, Any]:
        if resp.status_code in (200, 201, 202):
            return resp.json()

        try:
            body = resp.json()
        except ValueError:
            body = {}

        error = body.get("error", body)
        message = error.get("message", resp.text)
        code = error.get("code", "")

        if resp.status_code == 401:
            raise AuthenticationError(message, 401, code)
        if resp.status_code == 402:
            raise InsufficientBalanceError(message, 402, code)
        if resp.status_code == 429:
            retry_after = resp.headers.get("Retry-After")
            raise RateLimitError(message, float(retry_after) if retry_after else None)
        if resp.status_code == 400:
            raise ValidationError(message, 400, code)

        raise GTMRouterError(message, resp.status_code, code)
