#!/usr/bin/env python3
"""KateRTX SPV home API · 127.0.0.1:18773 only · visual + API surface.
NOT public · free thrift · home for SPV plugs under KateRTX/spvs + engine/spvs.
"""
from __future__ import annotations

import json
import os
import socket
import subprocess
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlparse

ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "out"
HOST = "127.0.0.1"
PORT = int(os.environ.get("KATERTX_API_PORT", "18773"))


def port_up(port: int) -> bool:
    try:
        with socket.create_connection((HOST, port), timeout=0.3):
            return True
    except Exception:
        return False


def list_js_plugs():
    d = ROOT / "spvs"
    out = []
    if not d.is_dir():
        return out
    for p in sorted(d.glob("*.js")):
        out.append(
            {
                "id": p.stem,
                "kind": "js",
                "path": str(p.relative_to(ROOT)),
                "dom": (d / f"{p.stem}.dom.html").is_file(),
            }
        )
    return out


def list_comp_plugs():
    d = ROOT / "engine" / "spvs"
    out = []
    if not d.is_dir():
        return out
    for p in sorted(d.glob("*.comp")):
        spv = d / f"{p.stem}.spv"
        out.append(
            {
                "id": p.stem,
                "kind": "comp",
                "path": str(p.relative_to(ROOT)),
                "spv": spv.is_file(),
            }
        )
    return out


def detect_bundle():
    binz = ROOT / "bin" / "krtx-zero2d"
    if binz.is_file():
        try:
            r = subprocess.run(
                [str(binz), "--detect"],
                capture_output=True,
                text=True,
                timeout=12,
                env={**os.environ, "DISPLAY": os.environ.get("DISPLAY", ":0")},
            )
            if r.stdout.strip().startswith("{"):
                return json.loads(r.stdout)
        except Exception as e:
            return {"error": str(e), "word": "KATE_DETECT"}
    # soft fallback without binary
    return {
        "word": "KATE_DETECT",
        "home": "KateRTX",
        "wayland": bool(os.environ.get("WAYLAND_DISPLAY")),
        "x11": bool(os.environ.get("DISPLAY")),
        "x_display": os.environ.get("DISPLAY") or "",
        "path": "unknown",
        "note": "build bin/krtx-zero2d for full detect",
        "law": "KateRTX SPV home",
    }


def status_bundle():
    js = list_js_plugs()
    comp = list_comp_plugs()
    det = detect_bundle()
    return {
        "ts": time.strftime("%Y-%m-%dT%H:%M:%S"),
        "service": "krtx-home-api",
        "home": str(ROOT),
        "bind": f"{HOST}:{PORT}",
        "public": False,
        "spv_home": True,
        "counts": {"js_plugs": len(js), "comp_plugs": len(comp)},
        "bins": {
            "engine": (ROOT / "bin/krtx-engine").is_file(),
            "zero2d": (ROOT / "bin/krtx-zero2d").is_file(),
            "spv": (ROOT / "bin/krtx-spv").is_file(),
            "term": (ROOT / "bin/krtx-term").is_file(),
        },
        "detect": det,
        "demo": {
            "id": "zero2d",
            "cli": "./bin/krtx-zero2d",
            "detect": "./bin/krtx-zero2d --detect",
            "shot": "./bin/krtx-zero2d --frames 90 --dump out/zero2d.ppm",
            "js_plug": "spvs/zero2d.js",
            "comp": "engine/shaders/zero2d.comp",
        },
        "cli": {
            "list": "./bin/krtx-spv list",
            "in": "./bin/krtx-spv in zero2d",
            "engine": "./bin/launch-rtx",
            "os": "./bin/launch-os-desktop",
            "api": f"http://{HOST}:{PORT}/",
        },
        "law": "KateRTX = SPV home · native GL window · no host browser chrome · free thrift",
    }


def run_zero2d(frames: int = 60, dump: bool = True):
    binz = ROOT / "bin" / "krtx-zero2d"
    if not binz.is_file():
        return {"ok": False, "error": "missing bin/krtx-zero2d · ./build.sh"}
    OUT.mkdir(parents=True, exist_ok=True)
    dump_path = OUT / "zero2d.ppm"
    cmd = [str(binz), "--frames", str(max(1, frames))]
    if dump:
        cmd += ["--dump", str(dump_path)]
    log = OUT / "zero2d-run.log"
    try:
        r = subprocess.run(
            cmd,
            cwd=str(ROOT),
            capture_output=True,
            text=True,
            timeout=max(30, frames // 2 + 20),
            env={
                **os.environ,
                "DISPLAY": os.environ.get("DISPLAY", ":0"),
                "__GLX_VENDOR_LIBRARY_NAME": os.environ.get(
                    "__GLX_VENDOR_LIBRARY_NAME", "nvidia"
                ),
            },
        )
        log.write_text((r.stdout or "") + "\n" + (r.stderr or ""), encoding="utf-8")
        return {
            "ok": r.returncode == 0,
            "returncode": r.returncode,
            "frames": frames,
            "dump": str(dump_path) if dump_path.is_file() else None,
            "dump_bytes": dump_path.stat().st_size if dump_path.is_file() else 0,
            "detect": detect_bundle(),
            "stderr_tail": (r.stderr or "")[-400:],
            "law": "native window · no host OS chrome · KateRTX home",
        }
    except Exception as e:
        return {"ok": False, "error": str(e)}


class Handler(BaseHTTPRequestHandler):
    server_version = "KRTX-HomeAPI/1.0"

    def log_message(self, fmt, *args):
        pass

    def _cors(self):
        origin = self.headers.get("Origin", "")
        if origin.startswith("http://127.0.0.1") or origin.startswith("http://localhost"):
            self.send_header("Access-Control-Allow-Origin", origin)
        else:
            self.send_header("Access-Control-Allow-Origin", "http://127.0.0.1")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")

    def _send(self, code, obj, ctype="application/json; charset=utf-8"):
        if isinstance(obj, (dict, list)):
            body = json.dumps(obj, indent=2).encode("utf-8")
        elif isinstance(obj, bytes):
            body = obj
        else:
            body = str(obj).encode("utf-8")
        self.send_response(code)
        self._cors()
        self.send_header("Content-Type", ctype)
        self.send_header("Content-Length", str(len(body)))
        self.send_header("Cache-Control", "no-store")
        self.send_header("X-BGRTX-Home", "KateRTX")
        self.end_headers()
        self.wfile.write(body)

    def do_OPTIONS(self):
        self.send_response(204)
        self._cors()
        self.end_headers()

    def do_GET(self):
        u = urlparse(self.path)
        path = u.path.rstrip("/") or "/"
        if path in ("/", "/status"):
            self._send(200, status_bundle())
            return
        if path == "/detect":
            self._send(200, detect_bundle())
            return
        if path == "/plugs":
            self._send(
                200,
                {
                    "ts": time.strftime("%Y-%m-%dT%H:%M:%S"),
                    "js": list_js_plugs(),
                    "comp": list_comp_plugs(),
                    "home": "KateRTX",
                },
            )
            return
        if path == "/demo":
            self._send(
                200,
                {
                    "id": "zero2d",
                    "title": "2D zero-cost demo",
                    "cli": status_bundle()["demo"],
                    "detect": detect_bundle(),
                    "post": "POST /demo/run {frames?:60}",
                },
            )
            return
        if path in ("/demo/frame", "/frame"):
            ppm = OUT / "zero2d.ppm"
            if not ppm.is_file():
                self._send(404, {"error": "no frame · POST /demo/run first"})
                return
            # serve ppm raw
            self._send(200, ppm.read_bytes(), "image/x-portable-pixmap")
            return
        if path == "/routes":
            self._send(
                200,
                {
                    "routes": [
                        "/",
                        "/status",
                        "/detect",
                        "/plugs",
                        "/demo",
                        "/demo/frame",
                        "POST /demo/run",
                    ]
                },
            )
            return
        self._send(404, {"error": "not found", "path": path})

    def do_POST(self):
        u = urlparse(self.path)
        path = u.path.rstrip("/") or "/"
        length = int(self.headers.get("Content-Length") or 0)
        raw = self.rfile.read(length) if length else b"{}"
        try:
            body = json.loads(raw.decode("utf-8", errors="replace") or "{}")
        except Exception:
            body = {}
        if path in ("/demo/run", "/demo", "/zero2d"):
            frames = int(body.get("frames") or 60)
            self._send(200, run_zero2d(frames=frames, dump=True))
            return
        self._send(404, {"error": "not found", "path": path})


def main():
    OUT.mkdir(parents=True, exist_ok=True)
    # single instance
    if port_up(PORT):
        print(f"krtx-home-api already on http://{HOST}:{PORT}/")
        return
    httpd = ThreadingHTTPServer((HOST, PORT), Handler)
    print(f"KateRTX home API  http://{HOST}:{PORT}/  (127 only · SPV home)")
    print(f"root={ROOT}")
    httpd.serve_forever()


if __name__ == "__main__":
    main()
