#!/usr/bin/env python3
"""Loopback-only RoboPark desktop supervisor for noVNC mesh relay."""
import argparse
import json
import os
import pwd
import shutil
import signal
import subprocess
import time
from pathlib import Path

running = True
children = []

def stop(*_):
    global running
    running = False

def desktop_user(config):
    preferred = config.get("desktop_user") or os.environ.get("SUDO_USER")
    if preferred and preferred != "root":
        return pwd.getpwnam(preferred)
    candidates = [entry for entry in pwd.getpwall() if 1000 <= entry.pw_uid < 65534 and entry.pw_shell not in ("/usr/sbin/nologin", "/bin/false")]
    if not candidates:
        raise RuntimeError("no desktop user found; rerun with --desktop-user USER")
    return candidates[0]

def command(config):
    user = desktop_user(config)
    runtime = Path(f"/run/user/{user.pw_uid}")
    wayland = sorted(runtime.glob("wayland-*"))
    if wayland and shutil.which("wayvnc"):
        return ["runuser", "-u", user.pw_name, "--", "env", f"XDG_RUNTIME_DIR={runtime}", f"WAYLAND_DISPLAY={wayland[0].name}", "wayvnc", "127.0.0.1", "5900"]
    if Path("/tmp/.X11-unix/X0").exists() and shutil.which("x11vnc"):
        return ["x11vnc", "-display", ":0", "-localhost", "-forever", "-shared", "-rfbport", "5900", "-nopw"]
    raise RuntimeError("no supported desktop found (install wayvnc for Wayland or x11vnc for X11)")

def spawn_all(config):
    vnc = subprocess.Popen(command(config))
    web = shutil.which("websockify")
    novnc = next((p for p in ("/usr/share/novnc", "/usr/share/novnc/utils/../") if Path(p).exists()), None)
    if not web or not novnc:
        vnc.terminate()
        raise RuntimeError("websockify/noVNC missing; run `sudo robopark screen install`")
    proxy = subprocess.Popen([web, "--web", novnc, "127.0.0.1:6080", "127.0.0.1:5900"])
    return [vnc, proxy]

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--config", required=True)
    args = parser.parse_args()
    config = json.loads(Path(args.config).read_text())
    signal.signal(signal.SIGTERM, stop)
    signal.signal(signal.SIGINT, stop)
    global children
    while running:
        try:
            children = spawn_all(config)
            print(f"[screen] ready: noVNC loopback 127.0.0.1:6080, VNC loopback 127.0.0.1:5900", flush=True)
            while running and all(child.poll() is None for child in children):
                time.sleep(1)
        except Exception as exc:
            print(f"[screen] unavailable: {exc}", flush=True)
        for child in children:
            if child.poll() is None:
                child.terminate()
        for child in children:
            try: child.wait(timeout=5)
            except subprocess.TimeoutExpired: child.kill()
        children = []
        if running: time.sleep(3)

if __name__ == "__main__":
    main()
