"""Move active OBS Browser sources from the public producer origin to the private loopback relay."""
from __future__ import annotations

import argparse
import json
import os
import sys
from pathlib import Path
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit

_HERE = Path(__file__).resolve().parent
if str(_HERE) not in sys.path:
    sys.path.insert(0, str(_HERE))

from director_obs import OBSController  # noqa: E402

PUBLIC_PRODUCER_HOST = "broadcast.viewregatta.com"
LOOPBACK_PORT = 8766


def is_cloud_producer(host: str | None) -> bool:
    value = (host or "").lower()
    return value == PUBLIC_PRODUCER_HOST or (
        value.startswith("trap-v3-producer-")
        and (value.endswith(".run.app") or value.endswith(".a.run.app"))
    )


def private_obs_url(url: str) -> str:
    """Rewrite only the known producer origin, preserving non-secret renderer parameters."""
    parsed = urlsplit(url)
    if (
        parsed.hostname in {"localhost", "127.0.0.1"}
        and parsed.port == LOOPBACK_PORT
        and parsed.path.endswith("/standings.html")
    ):
        return urlunsplit(("http", "127.0.0.1:8767", parsed.path, parsed.query, ""))
    if not is_cloud_producer(parsed.hostname):
        return url
    # Split high-connection single-camera pages across the second Chromium loopback origin. Both
    # names terminate at the same process and neither can be reached from the LAN.
    single_camera = parsed.path.endswith("/boatcam.html")
    host = "127.0.0.1" if single_camera else "localhost"
    query = [(name, value) for name, value in parse_qsl(parsed.query, keep_blank_values=True)
             if name.lower() not in {"token", "feed_token"}]
    return urlunsplit(("http", f"{host}:{LOOPBACK_PORT}", parsed.path, urlencode(query), ""))


def _attr(obj, *names, default=None):
    for name in names:
        if isinstance(obj, dict) and name in obj:
            return obj[name]
        if hasattr(obj, name):
            return getattr(obj, name)
    return default


def migrate(obs: OBSController, *, apply: bool) -> list[tuple[str, str]]:
    """Return ``(input, private URL)`` changes and optionally apply them to the connected OBS."""
    response = obs.client.get_input_list("browser_source")
    changes: list[tuple[str, str]] = []
    for item in _attr(response, "inputs", default=[]) or []:
        name = _attr(item, "inputName", "input_name")
        if not name:
            continue
        settings = obs.client.get_input_settings(name)
        current = str(_attr(settings, "inputSettings", "input_settings", default={}).get("url") or "")
        private = private_obs_url(current)
        if private == current:
            continue
        changes.append((name, private))
        if apply:
            obs.client.set_input_settings(name, {"url": private}, True)
            obs.refresh_browser_input(name)
    return changes


def migrate_scene_document(document: dict) -> list[tuple[str, str]]:
    """Rewrite producer URLs in an OBS native scene document while OBS is closed."""
    changes: list[tuple[str, str]] = []
    for source in document.get("sources") or []:
        settings = source.get("settings") or {}
        current = str(settings.get("url") or "")
        private = private_obs_url(current)
        if private == current:
            continue
        settings["url"] = private
        changes.append((str(source.get("name") or "?"), private))
    return changes


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--apply", action="store_true", help="write the changes; default is a dry run")
    parser.add_argument("--obs-host", default="localhost")
    parser.add_argument("--obs-port", type=int, default=4455)
    parser.add_argument("--obs-password", default="")
    parser.add_argument("--scene-file", type=Path, help="migrate a native OBS collection while OBS is closed")
    args = parser.parse_args(argv)

    if args.scene_file:
        document = json.loads(args.scene_file.read_text(encoding="utf-8-sig"))
        changes = migrate_scene_document(document)
        if args.apply and changes:
            temporary = args.scene_file.with_suffix(args.scene_file.suffix + ".tmp")
            temporary.write_text(
                json.dumps(document, indent=4, ensure_ascii=False) + "\n", encoding="utf-8"
            )
            temporary.replace(args.scene_file)
    else:
        obs = OBSController(
            host=args.obs_host,
            port=args.obs_port,
            password=args.obs_password or os.environ.get("OBS_WS_PASSWORD", ""),
        )
        obs.connect()
        try:
            changes = migrate(obs, apply=args.apply)
        finally:
            obs.disconnect()
    action = "changed" if args.apply else "would change"
    for name, url in changes:
        parsed = urlsplit(url)
        print(f"{action}: {name} -> {parsed.scheme}://{parsed.netloc}{parsed.path}")
    print(f"{action}: {len(changes)} Browser source(s)")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
