"""
HTTP Client with automatic retry, timeout, and auth injection.
"""

from __future__ import annotations

import asyncio
import json
from dataclasses import dataclass, field
from typing import Any

import aiohttp

from dvgateway.auth.manager import AuthManager
from dvgateway.types import Logger


@dataclass
class HttpResponse:
    status: int = 0
    headers: dict[str, str] = field(default_factory=dict)
    data: Any = None


class HttpClient:
    """HTTP client wrapping aiohttp with retry, timeout, and auth."""

    _RETRYABLE_STATUSES = {429, 502, 503, 504}

    def __init__(
        self,
        auth: AuthManager,
        timeout_ms: int,
        logger: Logger,
        tenant_id: str | None = None,
    ) -> None:
        self._auth = auth
        self._default_timeout = timeout_ms
        self._logger = logger
        self._tenant_id = tenant_id

    async def request(
        self,
        url: str,
        *,
        method: str = "GET",
        headers: dict[str, str] | None = None,
        body: str | bytes | None = None,
        timeout_ms: int | None = None,
        max_retries: int = 3,
        use_auth: bool = True,
    ) -> HttpResponse:
        timeout = (timeout_ms or self._default_timeout) / 1000.0
        req_headers: dict[str, str] = {"Content-Type": "application/json", **(headers or {})}

        if use_auth:
            req_headers["Authorization"] = await self._auth.get_bearer_header()

        if self._tenant_id:
            req_headers["X-Tenant-ID"] = self._tenant_id

        last_error: Exception | None = None

        for attempt in range(max_retries + 1):
            try:
                async with aiohttp.ClientSession() as session:
                    async with session.request(
                        method,
                        url,
                        headers=req_headers,
                        data=body,
                        timeout=aiohttp.ClientTimeout(total=timeout),
                    ) as resp:
                        if not (200 <= resp.status < 300) and resp.status in self._RETRYABLE_STATUSES and attempt < max_retries:
                            delay = min(1.0 * (2**attempt), 16.0)
                            self._logger.warn(
                                {"url": url, "status": resp.status, "attempt": attempt, "retry_in_ms": delay * 1000},
                                "HTTP request failed, retrying",
                            )
                            await asyncio.sleep(delay)
                            continue

                        content_type = resp.headers.get("Content-Type", "")
                        if "application/json" in content_type:
                            data = await resp.json()
                        else:
                            data = await resp.text()

                        return HttpResponse(
                            status=resp.status,
                            headers=dict(resp.headers),
                            data=data,
                        )

            except Exception as e:
                last_error = e
                if attempt < max_retries:
                    delay = min(1.0 * (2**attempt), 16.0)
                    self._logger.warn(
                        {"url": url, "err": str(e), "attempt": attempt, "retry_in_ms": delay * 1000},
                        "HTTP request error, retrying",
                    )
                    await asyncio.sleep(delay)

        raise last_error or RuntimeError(f"HTTP request failed after {max_retries} retries")

    async def get(self, path: str, **kwargs: Any) -> HttpResponse:
        return await self.request(self._auth.build_http_url(path), method="GET", **kwargs)

    async def post(self, path: str, body: Any = None, **kwargs: Any) -> HttpResponse:
        json_body = json.dumps(body) if body is not None else None
        return await self.request(self._auth.build_http_url(path), method="POST", body=json_body, **kwargs)

    async def put(self, path: str, body: Any = None, **kwargs: Any) -> HttpResponse:
        json_body = json.dumps(body) if body is not None else None
        return await self.request(self._auth.build_http_url(path), method="PUT", body=json_body, **kwargs)

    async def post_binary(self, path: str, data: bytes, content_type: str) -> None:
        await self.request(
            self._auth.build_http_url(path),
            method="POST",
            headers={"Content-Type": content_type},
            body=data,
        )
