#!/usr/bin/env python3
"""
PulseField local bridge — surroundmix-bridge
============================================

A tiny zero-dependency local server that lets the PulseField web app apply
PulseAudio + LADSPA configurations to your machine with one click. Presets
loaded via the LOAD button and RUN LIVE both route through this bridge, so
settings go live instantly while audio is playing.

It binds to 127.0.0.1:8765 (localhost only) and exposes a single endpoint:

    POST /apply   { "script": "<bash config>" }   ->  { "ok": true|false, ... }

Run it on the Linux machine that hosts your PulseAudio server:

    python3 surroundmix-bridge.py

Then click RUN LIVE (or LOAD a preset) in the web app — settings apply
instantly. If the bridge is not running, RUN LIVE automatically falls back
to downloading the script.

Requirements:
  - Python 3 (standard library only)
  - PulseAudio (`pactl`) running
  - LADSPA swh-plugins (`mbeq_1197`) installed

Security note: this server only listens on localhost and executes the bash
script sent to it. Only run it on a machine you trust, and stop it
(Ctrl+C) when you are not actively using PulseField.
"""

import json
import subprocess
from http.server import BaseHTTPRequestHandler, HTTPServer
from http import HTTPStatus

PORT = 8765


def unload_existing_ladspa():
    """Unload any LADSPA sinks we previously loaded so re-applies don't stack."""
    try:
        out = subprocess.check_output(
            ["pactl", "list", "short", "modules"], text=True, stderr=subprocess.DEVNULL
        )
    except Exception:
        return
    for line in out.splitlines():
        parts = line.split("\t")
        if len(parts) >= 2 and "module-ladspa-sink" in line and "ladspa_" in line:
            try:
                subprocess.run(
                    ["pactl", "unload-module", parts[0]],
                    check=False,
                    capture_output=True,
                )
            except Exception:
                pass


class Handler(BaseHTTPRequestHandler):
    def _cors(self):
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        # Required by Chrome's Private Network Access for https -> http://localhost
        self.send_header("Access-Control-Allow-Private-Network", "true")
        self.send_header("Access-Control-Max-Age", "86400")

    def _send_json(self, status, payload):
        self.send_response(status)
        self._cors()
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(payload).encode())

    def do_OPTIONS(self):
        self.send_response(HTTPStatus.NO_CONTENT)
        self._cors()
        self.end_headers()

    def do_POST(self):
        if self.path != "/apply":
            self._send_json(404, {"ok": False, "error": "not found"})
            return

        length = int(self.headers.get("Content-Length", 0) or 0)
        body = self.rfile.read(length) if length else b""
        try:
            data = json.loads(body) if body else {}
        except Exception:
            self._send_json(400, {"ok": False, "error": "invalid json"})
            return

        script = data.get("script", "")
        if not script or not script.strip():
            self._send_json(400, {"ok": False, "error": "no script provided"})
            return

        unload_existing_ladspa()
        try:
            proc = subprocess.run(
                ["bash", "-c", script],
                capture_output=True,
                text=True,
                timeout=30,
            )
            self._send_json(
                200 if proc.returncode == 0 else 502,
                {
                    "ok": proc.returncode == 0,
                    "stdout": proc.stdout[-2000:],
                    "stderr": proc.stderr[-2000:],
                },
            )
        except Exception as exc:
            self._send_json(500, {"ok": False, "error": str(exc)})

    def log_message(self, *args):
        pass


def main():
    print(f"surroundmix-bridge listening on http://127.0.0.1:{PORT}")
    print("Press Ctrl+C to stop.")
    try:
        HTTPServer(("127.0.0.1", PORT), Handler).serve_forever()
    except KeyboardInterrupt:
        print("\nStopped.")


if __name__ == "__main__":
    main()
