"""HTTP client with persistent connection pool and retries.

Uses a single httpx.Client instance for all requests, enabling
TCP connection reuse and TLS session resumption.
"""

from __future__ import annotations

import math
import random
import re
import time
from typing import Any, Optional

import httpx

from .errors import (
    ConnectionError,
    GenosisError,
    TimeoutError,
)
from .version import VERSION

RETRYABLE_STATUS_CODES = {408, 409, 429, 500, 502, 503, 504}


class HttpResponse:
    def __init__(self, data: Any, status: int, headers: httpx.Headers) -> None:
        self.data = data
        self.status = status
        self.headers = headers


class HttpClient:
    """HTTP client with persistent connection pool.

    A single httpx.Client is created at init and reused for all requests.
    This enables TCP connection reuse and avoids per-request TLS handshakes.
    """

    def __init__(
        self,
        api_key: str,
        base_url: str,
        max_retries: int = 2,
        timeout: float = 60.0,
    ) -> None:
        self.api_key = api_key
        base_url = base_url.rstrip("/")
        # Enforce HTTPS except for localhost/127.0.0.1
        if not base_url.startswith("https://") and not re.match(
            r"^http://(localhost|127\.0\.0\.1)(:|/|$)", base_url
        ):
            raise ValueError(
                "base_url must use HTTPS. API keys must not be transmitted over plaintext HTTP."
            )
        self.base_url = base_url
        self.max_retries = max_retries
        self.timeout = timeout

        # Persistent client — reused for all requests
        self._client = httpx.Client(
            base_url=self.base_url,
            headers={
                "Authorization": f"Bearer {self.api_key}",
                "User-Agent": f"genosis-sdk-python/{VERSION}",
            },
            timeout=self.timeout,
        )

    def get(self, path: str, headers: Optional[dict[str, str]] = None) -> HttpResponse:
        return self._request("GET", path, headers=headers)

    def post(self, path: str, body: Any = None) -> HttpResponse:
        return self._request("POST", path, body=body)

    def put(self, path: str, body: Any = None) -> HttpResponse:
        return self._request("PUT", path, body=body)

    def delete(self, path: str) -> HttpResponse:
        return self._request("DELETE", path)

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

    def _request(
        self,
        method: str,
        path: str,
        body: Any = None,
        headers: Optional[dict[str, str]] = None,
    ) -> HttpResponse:
        req_headers: dict[str, str] = {}
        if body is not None:
            req_headers["Content-Type"] = "application/json"
        if headers:
            req_headers.update(headers)

        url = path  # base_url is set on the client
        last_error: Optional[Exception] = None
        last_retry_after: Optional[str] = None

        for attempt in range(self.max_retries + 1):
            if attempt > 0:
                delay = self._calculate_delay(attempt, last_retry_after)
                time.sleep(delay)

            try:
                resp = self._client.request(
                    method,
                    url,
                    headers=req_headers,
                    json=body,
                )

                if resp.status_code == 304:
                    return HttpResponse(None, 304, resp.headers)

                try:
                    json_data = resp.json()
                except Exception:
                    json_data = {}

                if not resp.is_success:
                    err_body = json_data.get("error", {}) if isinstance(json_data, dict) else {}
                    message = err_body.get("message", f"HTTP {resp.status_code}")
                    code = err_body.get("code", "UNKNOWN")

                    if resp.status_code in RETRYABLE_STATUS_CODES and attempt < self.max_retries:
                        last_error = GenosisError.from_status(resp.status_code, message, code)
                        last_retry_after = resp.headers.get("retry-after")
                        continue

                    raise GenosisError.from_status(resp.status_code, message, code)

                return HttpResponse(json_data, resp.status_code, resp.headers)

            except GenosisError:
                raise
            except httpx.TimeoutException as exc:
                msg = f"Request timed out after {self.timeout}s"
                if attempt < self.max_retries:
                    last_error = TimeoutError(msg)
                    continue
                raise TimeoutError(msg) from exc
            except httpx.RequestError as exc:
                msg = str(exc) or "Connection failed"
                if attempt < self.max_retries:
                    last_error = ConnectionError(msg)
                    continue
                raise ConnectionError(msg) from exc

        raise last_error or ConnectionError("Request failed")

    def _calculate_delay(self, attempt: int, retry_after: Optional[str]) -> float:
        base = min(0.5 * (2 ** (attempt - 1)), 8.0)
        if retry_after:
            try:
                server_delay = float(retry_after)
                base = max(base, server_delay)
            except (ValueError, TypeError):
                pass
        jitter = base * 0.5 * random.random()
        return base + jitter
