"""Stdlib loopback HTTP server for ui_web + /v1 bridge API."""

from __future__ import annotations

import json
import mimetypes
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
from urllib.parse import parse_qs, urlparse

from .auth import extract_bearer_token
from .snapshots import (
    build_avatar_prompt_response,
    build_avatar_session,
    build_device_context_snapshot,
    build_healthz,
    build_permissions_presets,
    build_status_snapshot,
    build_updates_snapshot,
)

DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 8765


def default_ui_web_root() -> Path:
    return Path(__file__).resolve().parent.parent / "ui_web"


class BridgeState:
    def __init__(
        self,
        *,
        token: str,
        ui_root: Path,
        config_path: Path | None = None,
    ) -> None:
        self.token = token
        self.ui_root = ui_root.resolve()
        self.config_path = config_path


def _json_bytes(payload: dict[str, Any], *, status: int = 200) -> tuple[int, bytes, str]:
    body = json.dumps(payload, ensure_ascii=True, separators=(",", ":")).encode("utf-8")
    return status, body, "application/json; charset=utf-8"


def make_handler(state: BridgeState):  # noqa: ANN201
    class Handler(BaseHTTPRequestHandler):
        protocol_version = "HTTP/1.1"

        def log_message(self, format: str, *args: Any) -> None:  # noqa: A003
            return

        def _send(self, status: int, body: bytes, content_type: str) -> None:
            self.send_response(status)
            self.send_header("Content-Type", content_type)
            self.send_header("Content-Length", str(len(body)))
            self.send_header("Cache-Control", "no-store")
            self.send_header("X-IuVe-UI-Surface", "native_console")
            self.end_headers()
            self.wfile.write(body)

        def _unauthorized(self) -> None:
            status, body, ctype = _json_bytes(
                {"ok": False, "error": "unauthorized", "hint": "Provide Bearer token or ?token="},
                status=401,
            )
            self._send(status, body, ctype)

        def _require_auth(self, query: dict[str, list[str]]) -> bool:
            q_token = (query.get("token") or [None])[0]
            got = extract_bearer_token(self.headers.get("Authorization"), q_token)
            return bool(got) and secrets_compare(got, state.token)

        def do_GET(self) -> None:  # noqa: N802
            parsed = urlparse(self.path)
            path = parsed.path or "/"
            query = parse_qs(parsed.query)

            if path == "/healthz":
                status, body, ctype = _json_bytes(build_healthz())
                self._send(status, body, ctype)
                return

            if path.startswith("/v1/"):
                if not self._require_auth(query):
                    self._unauthorized()
                    return
                if path == "/v1/status":
                    lang = (query.get("lang") or ["en"])[0] or "en"
                    status, body, ctype = _json_bytes(
                        build_status_snapshot(config_path=state.config_path, lang=str(lang))
                    )
                    self._send(status, body, ctype)
                    return
                if path == "/v1/device_context":
                    status, body, ctype = _json_bytes(
                        build_device_context_snapshot(config_path=state.config_path)
                    )
                    self._send(status, body, ctype)
                    return
                if path == "/v1/avatar/session":
                    status, body, ctype = _json_bytes(build_avatar_session(config_path=state.config_path))
                    self._send(status, body, ctype)
                    return
                if path == "/v1/permissions/presets":
                    status, body, ctype = _json_bytes(
                        build_permissions_presets(config_path=state.config_path)
                    )
                    self._send(status, body, ctype)
                    return
                if path == "/v1/updates":
                    status, body, ctype = _json_bytes(
                        build_updates_snapshot(config_path=state.config_path)
                    )
                    self._send(status, body, ctype)
                    return
                status, body, ctype = _json_bytes({"ok": False, "error": "not_found"}, status=404)
                self._send(status, body, ctype)
                return

            self._serve_static(path)

        def do_POST(self) -> None:  # noqa: N802
            parsed = urlparse(self.path)
            path = parsed.path or "/"
            query = parse_qs(parsed.query)
            if not path.startswith("/v1/"):
                status, body, ctype = _json_bytes({"ok": False, "error": "not_found"}, status=404)
                self._send(status, body, ctype)
                return
            if not self._require_auth(query):
                self._unauthorized()
                return

            length = int(self.headers.get("Content-Length") or 0)
            raw = self.rfile.read(length) if length > 0 else b"{}"
            try:
                payload = json.loads(raw.decode("utf-8") or "{}")
            except json.JSONDecodeError:
                status, body, ctype = _json_bytes({"ok": False, "error": "invalid_json"}, status=400)
                self._send(status, body, ctype)
                return
            if not isinstance(payload, dict):
                status, body, ctype = _json_bytes({"ok": False, "error": "invalid_json"}, status=400)
                self._send(status, body, ctype)
                return

            if path == "/v1/avatar/prompt":
                status, body, ctype = _json_bytes(
                    build_avatar_prompt_response(payload, config_path=state.config_path)
                )
                self._send(status, body, ctype)
                return

            status, body, ctype = _json_bytes({"ok": False, "error": "not_found"}, status=404)
            self._send(status, body, ctype)

        def _serve_static(self, path: str) -> None:
            rel = path.lstrip("/") or "index.html"
            if ".." in rel.split("/"):
                status, body, ctype = _json_bytes({"ok": False, "error": "forbidden"}, status=403)
                self._send(status, body, ctype)
                return
            target = (state.ui_root / rel).resolve()
            try:
                target.relative_to(state.ui_root)
            except ValueError:
                status, body, ctype = _json_bytes({"ok": False, "error": "forbidden"}, status=403)
                self._send(status, body, ctype)
                return
            if target.is_dir():
                target = target / "index.html"
            if not target.is_file():
                status, body, ctype = _json_bytes({"ok": False, "error": "not_found"}, status=404)
                self._send(status, body, ctype)
                return
            data = target.read_bytes()
            ctype = mimetypes.guess_type(str(target))[0] or "application/octet-stream"
            if ctype.startswith("text/") or ctype in {"application/javascript", "application/json"}:
                ctype = f"{ctype}; charset=utf-8"
            self._send(200, data, ctype)

    return Handler


def secrets_compare(a: str, b: str) -> bool:
    if len(a) != len(b):
        return False
    result = 0
    for x, y in zip(a.encode("utf-8"), b.encode("utf-8")):
        result |= x ^ y
    return result == 0


class UiBridgeServer:
    def __init__(
        self,
        *,
        token: str,
        host: str = DEFAULT_HOST,
        port: int = DEFAULT_PORT,
        ui_root: Path | None = None,
        config_path: Path | None = None,
    ) -> None:
        if host not in {"127.0.0.1", "localhost", "::1"}:
            raise ValueError("bridge_must_bind_loopback_only")
        self.host = "127.0.0.1" if host == "localhost" else host
        self.port = int(port)
        self.token = token
        self.ui_root = (ui_root or default_ui_web_root()).resolve()
        self.config_path = config_path
        self._httpd: ThreadingHTTPServer | None = None
        self._thread: threading.Thread | None = None

    @property
    def base_url(self) -> str:
        return f"http://{self.host}:{self.port}"

    @property
    def console_url(self) -> str:
        return f"{self.base_url}/?token={self.token}"

    def start(self, *, background: bool = False) -> None:
        state = BridgeState(token=self.token, ui_root=self.ui_root, config_path=self.config_path)
        handler = make_handler(state)
        self._httpd = ThreadingHTTPServer((self.host, self.port), handler)
        # Reflect OS-assigned port when bind port was 0.
        self.port = int(self._httpd.server_address[1])
        if background:
            self._thread = threading.Thread(target=self._httpd.serve_forever, name="iuve-ui-bridge", daemon=True)
            self._thread.start()
            return
        self._httpd.serve_forever()

    def stop(self) -> None:
        if self._httpd is not None:
            self._httpd.shutdown()
            self._httpd.server_close()
            self._httpd = None
        if self._thread is not None:
            self._thread.join(timeout=2.0)
            self._thread = None
