"""Proxy helpers extracted from `hiveos.observe`."""

from __future__ import annotations

import json
import threading
import urllib.error
import urllib.request
from contextlib import contextmanager
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


class ObserveProxyServer:
    def __init__(
        self,
        *,
        run_id,
        upstream_base,
        bind_host="127.0.0.1",
        bind_port=0,
        agent_id="observer.proxy",
        emit_event,
        shorten,
        now_ms,
    ):
        self.run_id = str(run_id or "")
        self.upstream_base = str(upstream_base or "").rstrip("/")
        self.bind_host = str(bind_host or "127.0.0.1")
        self.bind_port = int(bind_port or 0)
        self.agent_id = str(agent_id or "observer.proxy")
        self.emit_event = emit_event
        self.shorten = shorten
        self.now_ms = now_ms
        self._server = None
        self._thread = None
        self.url = ""

    def start(self):
        server_ref = self

        class _Handler(BaseHTTPRequestHandler):
            def do_POST(self):  # noqa: N802
                server_ref._handle_request(self)

            def do_GET(self):  # noqa: N802
                server_ref._handle_request(self)

            def log_message(self, fmt, *args):  # noqa: A003
                return

        self._server = ThreadingHTTPServer((self.bind_host, self.bind_port), _Handler)
        actual_host, actual_port = self._server.server_address
        self.url = f"http://{actual_host}:{actual_port}/v1"
        self._thread = threading.Thread(target=self._server.serve_forever, daemon=True, name="hive-observe-proxy")
        self._thread.start()
        self.emit_event(
            run_id=self.run_id,
            event_type="observe_proxy_started",
            outcome="success",
            payload={"proxy_url": self.url, "upstream_base": self.upstream_base, "source": "cli"},
            agent_id=self.agent_id,
        )
        return self

    def stop(self):
        if self._server:
            try:
                self._server.shutdown()
            except Exception:
                pass
            try:
                self._server.server_close()
            except Exception:
                pass
            self._server = None
        if self._thread:
            self._thread.join(timeout=1.0)
            self._thread = None
        if self.url:
            self.emit_event(
                run_id=self.run_id,
                event_type="observe_proxy_stopped",
                outcome="success",
                payload={"proxy_url": self.url, "source": "cli"},
                agent_id=self.agent_id,
            )

    def _target_url(self, path):
        p = str(path or "/")
        if not p.startswith("/"):
            p = "/" + p
        if self.upstream_base.endswith("/v1") and p.startswith("/v1/"):
            p = p[3:]
        if self.upstream_base.endswith("/v1") and p == "/v1":
            p = ""
        return f"{self.upstream_base}{p}"

    def _handle_request(self, handler):
        started = self.now_ms()
        content_length = int(handler.headers.get("Content-Length") or 0)
        body = handler.rfile.read(content_length) if content_length > 0 else b""
        path = str(handler.path or "/")

        self.emit_event(
            run_id=self.run_id,
            event_type="observe_proxy_request",
            outcome="success",
            payload={
                "method": str(handler.command or "GET"),
                "path": path,
                "body_bytes": len(body),
                "source": "proxy",
            },
            agent_id=self.agent_id,
        )

        url = self._target_url(path)
        forward_headers = {}
        for key, value in handler.headers.items():
            k = str(key or "")
            if k.lower() in {"host", "content-length"}:
                continue
            forward_headers[k] = value
        req = urllib.request.Request(url=url, data=(body if body else None), headers=forward_headers, method=handler.command)
        try:
            with urllib.request.urlopen(req, timeout=120) as resp:
                resp_body = resp.read()
                status_code = int(resp.status)
                resp_headers = dict(resp.headers.items())
            elapsed = max(0, self.now_ms() - started)
            self.emit_event(
                run_id=self.run_id,
                event_type="observe_proxy_response",
                outcome="success",
                payload={
                    "method": str(handler.command or "GET"),
                    "path": path,
                    "status_code": status_code,
                    "duration_ms": elapsed,
                    "body_tail": self.shorten(resp_body.decode("utf-8", errors="replace"), limit=2000),
                    "source": "proxy",
                },
                agent_id=self.agent_id,
            )
            handler.send_response(status_code)
            for header_key, header_value in resp_headers.items():
                hk = str(header_key or "")
                if hk.lower() in {"transfer-encoding", "content-length", "connection"}:
                    continue
                handler.send_header(hk, header_value)
            handler.send_header("Content-Length", str(len(resp_body)))
            handler.end_headers()
            if resp_body:
                handler.wfile.write(resp_body)
        except urllib.error.HTTPError as exc:
            err_body = exc.read() if hasattr(exc, "read") else b""
            status_code = int(getattr(exc, "code", 502) or 502)
            elapsed = max(0, self.now_ms() - started)
            self.emit_event(
                run_id=self.run_id,
                event_type="observe_proxy_response",
                outcome="failed",
                payload={
                    "method": str(handler.command or "GET"),
                    "path": path,
                    "status_code": status_code,
                    "duration_ms": elapsed,
                    "error": self.shorten(str(exc), limit=400),
                    "body_tail": self.shorten(err_body.decode("utf-8", errors="replace"), limit=2000),
                    "source": "proxy",
                },
                agent_id=self.agent_id,
            )
            handler.send_response(status_code)
            handler.send_header("Content-Type", "application/json")
            handler.send_header("Content-Length", str(len(err_body)))
            handler.end_headers()
            if err_body:
                handler.wfile.write(err_body)
        except Exception as exc:
            elapsed = max(0, self.now_ms() - started)
            payload = json.dumps({"error": "proxy_forward_failed", "detail": str(exc)}, separators=(",", ":")).encode("utf-8")
            self.emit_event(
                run_id=self.run_id,
                event_type="observe_proxy_response",
                outcome="failed",
                payload={
                    "method": str(handler.command or "GET"),
                    "path": path,
                    "status_code": 502,
                    "duration_ms": elapsed,
                    "error": self.shorten(str(exc), limit=400),
                    "source": "proxy",
                },
                agent_id=self.agent_id,
            )
            handler.send_response(502)
            handler.send_header("Content-Type", "application/json")
            handler.send_header("Content-Length", str(len(payload)))
            handler.end_headers()
            handler.wfile.write(payload)


@contextmanager
def observe_proxy(
    *,
    run_id,
    upstream_base,
    bind_host="127.0.0.1",
    bind_port=0,
    agent_id="observer.proxy",
    emit_event,
    shorten,
    now_ms,
):
    server = ObserveProxyServer(
        run_id=run_id,
        upstream_base=upstream_base,
        bind_host=bind_host,
        bind_port=bind_port,
        agent_id=agent_id,
        emit_event=emit_event,
        shorten=shorten,
        now_ms=now_ms,
    ).start()
    try:
        yield server
    finally:
        server.stop()


def build_proxy_env(proxy_url):
    url = str(proxy_url or "").strip().rstrip("/")
    return {
        "OPENAI_BASE_URL": url,
        "OPENAI_API_BASE": url,
    }

