"""Small, bounded HTTP transport with no third-party runtime dependency."""

from __future__ import annotations

import json
import ssl
import urllib.error
import urllib.request
from dataclasses import dataclass
from typing import Any, Dict, Mapping, Optional


class TransportError(RuntimeError):
    """Raised when a request cannot reach the remote service."""


class ResponseTooLarge(TransportError):
    """Raised when a response exceeds its configured safety limit."""


class _NoRedirectHandler(urllib.request.HTTPRedirectHandler):
    """Return redirect responses to the caller instead of following them."""

    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


@dataclass(frozen=True)
class HTTPResponse:
    """A bounded HTTP response."""

    status_code: int
    body: bytes
    headers: Mapping[str, str]

    def json(self) -> Any:
        """Decode and return the JSON response body."""
        try:
            return json.loads(self.body.decode("utf-8"))
        except (UnicodeDecodeError, json.JSONDecodeError) as exc:
            raise TransportError("Remote service returned invalid JSON") from exc


class UrllibTransport:
    """Perform HTTPS requests using the Python standard library."""

    def __init__(self, ssl_context: Optional[ssl.SSLContext] = None) -> None:
        self.ssl_context = ssl_context or ssl.create_default_context()

    def request(
        self,
        method: str,
        url: str,
        *,
        headers: Optional[Mapping[str, str]] = None,
        body: Optional[bytes] = None,
        timeout: float = 15.0,
        max_response_bytes: int = 2 * 1024 * 1024,
        allow_redirects: bool = True,
    ) -> HTTPResponse:
        """Send one request and read at most ``max_response_bytes``."""
        request = urllib.request.Request(
            url,
            data=body,
            headers=dict(headers or {}),
            method=method.upper(),
        )
        try:
            if allow_redirects:
                response = urllib.request.urlopen(  # noqa: S310 - configured URL
                    request,
                    timeout=timeout,
                    context=self.ssl_context,
                )
            else:
                opener = urllib.request.build_opener(
                    urllib.request.HTTPSHandler(context=self.ssl_context),
                    _NoRedirectHandler(),
                )
                response = opener.open(request, timeout=timeout)  # noqa: S310
        except urllib.error.HTTPError as exc:
            response = exc
        except (urllib.error.URLError, TimeoutError, OSError) as exc:
            raise TransportError("Remote service is unavailable") from exc

        try:
            payload = response.read(max_response_bytes + 1)
            if len(payload) > max_response_bytes:
                raise ResponseTooLarge(
                    "Remote response exceeds configured maximum size"
                )
            response_headers: Dict[str, str] = {
                str(key): str(value) for key, value in response.headers.items()
            }
            return HTTPResponse(
                status_code=int(response.getcode()),
                body=payload,
                headers=response_headers,
            )
        finally:
            response.close()


def json_body(payload: Any) -> bytes:
    """Return a deterministic UTF-8 JSON request body."""
    return json.dumps(
        payload,
        ensure_ascii=False,
        allow_nan=False,
        separators=(",", ":"),
        sort_keys=True,
    ).encode("utf-8")
