"""Narrow reverse proxy that makes local Hermes available through the mTLS node."""

from __future__ import annotations

import http.client
from dataclasses import dataclass
import json
import re
from typing import Any, Callable, Mapping
from urllib.parse import urlparse


_RUN_PATH = re.compile(
    r"^/v1/runs(?:/[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}(?:/(?:events|stop|approve|approval))?)?/?$"
)


def is_allowed_run_path(path: str) -> bool:
    """Return whether a request path belongs to the narrow Hermes surface."""

    return bool(_RUN_PATH.fullmatch(urlparse(path).path))


@dataclass(frozen=True, slots=True)
class ForwardResult:
    status: int
    content_type: str
    payload: bytes


class HermesProxy:
    def __init__(
        self,
        *,
        enabled: bool,
        base_url: str,
        api_key: str,
        timeout_seconds: float = 20.0,
    ) -> None:
        self.enabled = enabled
        self._parsed = urlparse(base_url)
        self._api_key = api_key
        self._timeout = timeout_seconds

    def health(self) -> dict[str, object]:
        if not self.enabled:
            return {"status": "disabled"}
        connection = self._connection(timeout=0.75)
        try:
            connection.request("GET", "/health", headers=self._headers({"Accept": "application/json"}))
            response = connection.getresponse()
            response.read(64 * 1024)
            return {"status": "ready" if 200 <= response.status < 300 else "unavailable"}
        except OSError as exc:
            return {"status": "unavailable", "detail": type(exc).__name__}
        finally:
            connection.close()

    def forward(
        self,
        handler: Any,
        *,
        method: str,
        path: str,
        body: Mapping[str, Any] | None,
        killed: Callable[[], bool],
    ) -> ForwardResult | None:
        parsed_path = urlparse(path)
        if not self.enabled or not is_allowed_run_path(path):
            self._json(handler, 404, {"error": "hermes_proxy_path_not_allowed"})
            return None
        encoded = None
        headers: dict[str, str] = {"Accept": handler.headers.get("Accept", "application/json")}
        for name in ("Idempotency-Key", "Last-Event-ID"):
            value = handler.headers.get(name)
            if value:
                headers[name] = value[:256]
        if body is not None:
            encoded = json.dumps(body, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
            headers["Content-Type"] = "application/json"
            headers["Content-Length"] = str(len(encoded))
        connection = self._connection(timeout=self._timeout)
        try:
            connection.request(
                method,
                parsed_path.path + (f"?{parsed_path.query}" if parsed_path.query else ""),
                body=encoded,
                headers=self._headers(headers),
            )
            response = connection.getresponse()
            content_type = response.getheader("Content-Type", "application/json")
            is_stream = "text/event-stream" in content_type.lower()
            if is_stream:
                handler.send_response(response.status)
                handler.send_header("Content-Type", content_type)
                handler.send_header("Cache-Control", "no-cache")
                handler.send_header("X-Content-Type-Options", "nosniff")
                handler.send_header("Connection", "close")
                handler.end_headers()
                reached_eof = False
                while not killed():
                    chunk = response.read(4096)
                    if not chunk:
                        reached_eof = True
                        break
                    handler.wfile.write(chunk)
                    handler.wfile.flush()
                return (
                    ForwardResult(response.status, content_type, b"")
                    if reached_eof
                    else None
                )
            else:
                payload = response.read(4 * 1024 * 1024 + 1)
                if len(payload) > 4 * 1024 * 1024:
                    self._json(handler, 502, {"error": "hermes_response_too_large"})
                    return None
                handler.send_response(response.status)
                handler.send_header("Content-Type", content_type)
                handler.send_header("Cache-Control", "no-store")
                handler.send_header("X-Content-Type-Options", "nosniff")
                handler.send_header("Content-Length", str(len(payload)))
                handler.end_headers()
                handler.wfile.write(payload)
                return ForwardResult(response.status, content_type, payload)
        except (OSError, http.client.HTTPException) as exc:
            self._json(handler, 502, {"error": "hermes_unavailable", "detail": type(exc).__name__})
            return None
        finally:
            connection.close()

    def _connection(self, *, timeout: float) -> http.client.HTTPConnection:
        host = self._parsed.hostname or "127.0.0.1"
        port = self._parsed.port or (443 if self._parsed.scheme == "https" else 80)
        if self._parsed.scheme == "https":
            return http.client.HTTPSConnection(host, port, timeout=timeout)
        return http.client.HTTPConnection(host, port, timeout=timeout)

    def _headers(self, headers: Mapping[str, str]) -> dict[str, str]:
        result = dict(headers)
        if self._api_key:
            result["Authorization"] = f"Bearer {self._api_key}"
            result["X-API-Key"] = self._api_key
        return result

    @staticmethod
    def _json(handler: Any, status: int, value: Mapping[str, object]) -> None:
        if getattr(handler, "_headers_buffer", None):
            # A downstream response may already have started. Closing is safer
            # than producing a syntactically mixed response.
            handler.close_connection = True
            return
        payload = json.dumps(value, separators=(",", ":")).encode("utf-8")
        handler.send_response(status)
        handler.send_header("Content-Type", "application/json")
        handler.send_header("Content-Length", str(len(payload)))
        handler.send_header("Cache-Control", "no-store")
        handler.end_headers()
        handler.wfile.write(payload)
