#!/usr/bin/env python3
"""Static frontend + /v1, /api reverse proxy on the same origin.

Replaces ``python3 -m http.server`` so the agora-frontend can be served
behind Tailscale Funnel without mixed-content blocks. Any path under
``/v1/`` or ``/api/`` is proxied to ``AGORA_BACKEND`` (default
http://localhost:8011); everything else is served as a static file from
this directory.

Usage::

    cd services/agora-frontend
    python3 proxy_server.py [PORT]
"""
from __future__ import annotations

import http.client
import os
import sys
from http.server import HTTPServer, SimpleHTTPRequestHandler
from urllib.parse import urlparse


PORT = int(os.environ.get("AGORA_FRONTEND_PORT", sys.argv[1] if len(sys.argv) > 1 else 8000))
BACKEND = os.environ.get("AGORA_BACKEND", "http://localhost:8011")
PROXY_PREFIXES = ("/v1/", "/api/", "/v1?", "/api?")
PROXY_EXACT = {"/v1", "/api"}

_p = urlparse(BACKEND)
BACKEND_HOST = _p.hostname or "localhost"
BACKEND_PORT = _p.port or (443 if _p.scheme == "https" else 80)
BACKEND_HTTPS = _p.scheme == "https"

HOP_BY_HOP = {
    "connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
    "te", "trailers", "transfer-encoding", "upgrade", "host", "content-length",
}


class FrontendProxy(SimpleHTTPRequestHandler):
    server_version = "AgoraFrontendProxy/1.0"

    def _is_proxy_path(self) -> bool:
        path = self.path
        if path in PROXY_EXACT:
            return True
        return path.startswith(PROXY_PREFIXES) or path.startswith("/v1/") or path.startswith("/api/")

    def _proxy(self, method: str) -> None:
        body = None
        clen = self.headers.get("Content-Length")
        if clen:
            try:
                body = self.rfile.read(int(clen))
            except Exception:
                body = b""
        outbound_headers = {
            k: v for k, v in self.headers.items()
            if k.lower() not in HOP_BY_HOP
        }
        outbound_headers["Host"] = f"{BACKEND_HOST}:{BACKEND_PORT}"
        outbound_headers["X-Forwarded-For"] = self.client_address[0]
        outbound_headers["X-Forwarded-Proto"] = self.headers.get("X-Forwarded-Proto", "http")
        try:
            ConnCls = http.client.HTTPSConnection if BACKEND_HTTPS else http.client.HTTPConnection
            conn = ConnCls(BACKEND_HOST, BACKEND_PORT, timeout=60)
            conn.request(method, self.path, body=body, headers=outbound_headers)
            resp = conn.getresponse()
        except Exception as e:
            self.send_error(502, f"upstream {BACKEND} unreachable: {e}")
            return
        try:
            self.send_response(resp.status, resp.reason)
            for k, v in resp.getheaders():
                if k.lower() in HOP_BY_HOP:
                    continue
                self.send_header(k, v)
            self.end_headers()
            while True:
                chunk = resp.read(8192)
                if not chunk:
                    break
                try:
                    self.wfile.write(chunk)
                except (BrokenPipeError, ConnectionResetError):
                    break
        finally:
            try: resp.close()
            except Exception: pass
            try: conn.close()
            except Exception: pass

    # Override every method so static + proxy paths share one handler
    def do_GET(self):
        if self._is_proxy_path():
            return self._proxy("GET")
        return super().do_GET()

    def do_POST(self):    return self._proxy("POST")    if self._is_proxy_path() else self.send_error(405)
    def do_PUT(self):     return self._proxy("PUT")     if self._is_proxy_path() else self.send_error(405)
    def do_PATCH(self):   return self._proxy("PATCH")   if self._is_proxy_path() else self.send_error(405)
    def do_DELETE(self):  return self._proxy("DELETE")  if self._is_proxy_path() else self.send_error(405)
    def do_OPTIONS(self): return self._proxy("OPTIONS") if self._is_proxy_path() else self.send_error(204)
    def do_HEAD(self):
        if self._is_proxy_path():
            return self._proxy("HEAD")
        return super().do_HEAD()


class _ThreadedServer(HTTPServer):
    daemon_threads = True


def main() -> None:
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
    httpd = _ThreadedServer(("0.0.0.0", PORT), FrontendProxy)
    print(f"agora-frontend proxy · :{PORT}  →  static · /v1 /api → {BACKEND}", flush=True)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()
