"""
Hermes-UI Reverse-Proxy — bettet das Hermes-Web-Dashboard in Mission Control ein.

Das Hermes-Agent-Framework bringt eine fertige Web-UI mit (Chat mit Live-Tool-
Aktivitaet, Approval-Prompts, Settings, Sessions). Sie laeuft als eigener Dienst
lokal auf der Box (`hermes dashboard`, Port 9119, an 127.0.0.1 gebunden). MC
proxyt sie unter `/hermes-ui/` und bettet sie per iframe ein:

  - Das Dashboard ist explizit fuer Prefix-Reverse-Proxy gebaut: wir setzen
    `X-Forwarded-Prefix: /hermes-ui`, dann rewritet es index.html/CSS/Asset-URLs
    und seinen SPA-Base-Path selbst.
  - Auf Loopback injiziert das Dashboard seinen eigenen Session-Token in die SPA
    -> kein zweiter Login. MC bleibt das eine Gateway (LAN-only).

Damit faellt der fruehere Eigenbau-Chat (Proxy auf :8642) weg.
"""

import asyncio

import httpx
import websockets
from fastapi import APIRouter, Request, WebSocket
from fastapi.responses import RedirectResponse, Response
from starlette.websockets import WebSocketState

from config import HERMES_DASHBOARD_URL

router = APIRouter()

PREFIX = "/hermes-ui"
_WS_BASE = HERMES_DASHBOARD_URL.replace("http://", "ws://").replace("https://", "wss://")

# Hop-by-hop-Header, die ein Proxy nicht weiterreichen darf (RFC 7230) plus solche,
# die httpx/Starlette selbst neu berechnen (Laenge/Encoding).
_HOP = {
    "host", "content-length", "connection", "keep-alive", "transfer-encoding",
    "content-encoding", "upgrade", "proxy-authenticate", "proxy-authorization",
    "te", "trailers",
}

_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"]


@router.get(PREFIX)
def _hermes_ui_root_redirect():
    """Bare /hermes-ui -> /hermes-ui/ (sonst greift das Asset-Prefix-Rewriting nicht)."""
    return RedirectResponse(url=PREFIX + "/")


@router.api_route(PREFIX + "/{path:path}", methods=_METHODS)
async def hermes_ui_proxy(request: Request, path: str):
    """HTTP-Reverse-Proxy auf das Hermes-Dashboard mit Prefix-Header."""
    url = f"{HERMES_DASHBOARD_URL}/{path}"
    headers = {k: v for k, v in request.headers.items() if k.lower() not in _HOP}
    headers["X-Forwarded-Prefix"] = PREFIX
    body = await request.body()
    timeout = httpx.Timeout(60.0, connect=10.0)
    async with httpx.AsyncClient(timeout=timeout, follow_redirects=False) as client:
        r = await client.request(
            request.method, url,
            params=request.query_params, headers=headers, content=body,
        )
    resp_headers = {k: v for k, v in r.headers.items() if k.lower() not in _HOP}
    return Response(
        content=r.content, status_code=r.status_code,
        headers=resp_headers, media_type=r.headers.get("content-type"),
    )


@router.websocket(PREFIX + "/api/{name}")
async def hermes_ui_ws(ws: WebSocket, name: str):
    """WebSocket-Bruecke fuer die Chat-/Event-WS des Dashboards (pty/ws/pub/events)."""
    await ws.accept()
    qs = ws.url.query
    target = f"{_WS_BASE}/api/{name}" + (f"?{qs}" if qs else "")
    try:
        async with websockets.connect(
            target,
            additional_headers={"X-Forwarded-Prefix": PREFIX},
            max_size=None, open_timeout=10, ping_interval=None,
        ) as up:

            async def client_to_upstream():
                try:
                    while True:
                        msg = await ws.receive()
                        if msg.get("type") == "websocket.disconnect":
                            break
                        if msg.get("text") is not None:
                            await up.send(msg["text"])
                        elif msg.get("bytes") is not None:
                            await up.send(msg["bytes"])
                except Exception:
                    pass
                finally:
                    await up.close()

            async def upstream_to_client():
                try:
                    async for m in up:
                        if isinstance(m, (bytes, bytearray)):
                            await ws.send_bytes(m)
                        else:
                            await ws.send_text(m)
                except Exception:
                    pass
                finally:
                    if ws.application_state != WebSocketState.DISCONNECTED:
                        await ws.close()

            await asyncio.gather(client_to_upstream(), upstream_to_client())
    except Exception:
        if ws.application_state != WebSocketState.DISCONNECTED:
            try:
                await ws.close()
            except Exception:
                pass
