"""
Hermes gateway LaunchAgent management.

The Beacon→Hermes gateway (port 8643) is a thin shim that runs **inside the user's
personal Hermes install** (`~/.hermes/hermes-agent/venv`) — `launch_gateway.py` exits
if that install is absent. So it is deliberately NOT bundled into the portable app /
`.pkg` and NOT bridge-managed: it gets its **own** LaunchAgent (RunAtLoad + KeepAlive),
which this module installs **conditionally, only on a machine that actually has Hermes**.

Wired into app startup (`main.py`) right after the bridge LaunchAgent: it self-checks
for Hermes and no-ops (and cleans up any stale gateway agent) on every other machine —
Linux/headless, or a Mac without Hermes. Mirrors `launchagent.py`'s patterns.
"""
from __future__ import annotations
import logging
import os
import subprocess
from pathlib import Path

log = logging.getLogger(__name__)

_LABEL = "app.terminull.hermes-gateway"
_PLIST_DIR = Path.home() / "Library" / "LaunchAgents"
_PLIST_PATH = _PLIST_DIR / f"{_LABEL}.plist"
_LOG_DIR = Path.home() / "Library" / "Logs" / "Terminull"

# Older labels this supersedes — retired (booted out + plist removed) on install so a
# machine never ends up running two gateways on :8643.
_OLD_LABELS = ("app.beacon.hermes-gateway",)

_HERMES_HOME = Path.home() / ".hermes"
_HERMES_ROOT = _HERMES_HOME / "hermes-agent"
_HERMES_PY = _HERMES_ROOT / "venv" / "bin" / "python3"


def hermes_available() -> bool:
    """True only on a machine with a usable personal Hermes install (the gateway's
    interpreter). The single gate for whether the gateway should exist here."""
    return _HERMES_PY.exists() and os.access(str(_HERMES_PY), os.X_OK)


def _launch_gateway_script() -> Path | None:
    """Absolute path to `launch_gateway.py` inside the running build's `terminull_bridge`
    (dev source tree or the app bundle's Resources — resolved from the package, so both
    work + a self-update bundle swap keeps it valid). None if the package can't be found."""
    try:
        import terminull_bridge  # noqa: PLC0415 — resolved at call time (post bridge import)
    except Exception:
        return None
    pkg_file = getattr(terminull_bridge, "__file__", None)
    if not pkg_file:
        return None
    pkg = Path(pkg_file).resolve().parent
    script = pkg / "gateways" / "hermes_plugin" / "launch_gateway.py"
    return script if script.exists() else None


def _plist_content(script: Path) -> str:
    _LOG_DIR.mkdir(parents=True, exist_ok=True)
    bridge_root = script.parent.parent.parent.parent   # …/terminull_bridge → its parent
    pythonpath = f"{bridge_root}:{_HERMES_ROOT}"
    path = f"{_HERMES_ROOT / 'venv' / 'bin'}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
    gw_log = _LOG_DIR / "hermes-gateway.log"
    return f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>{_LABEL}</string>

    <key>ProgramArguments</key>
    <array>
        <string>{_HERMES_PY}</string>
        <string>{script}</string>
    </array>

    <key>EnvironmentVariables</key>
    <dict>
        <key>PYTHONPATH</key>
        <string>{pythonpath}</string>
        <key>PATH</key>
        <string>{path}</string>
        <key>HERMES_HOME</key>
        <string>{_HERMES_HOME}</string>
    </dict>

    <!-- Relaunch on an unclean death (crash / signal), NOT on a clean exit — matches
         the bridge LaunchAgent. `launch_gateway.py` exits cleanly when Hermes is gone. -->
    <key>KeepAlive</key>
    <dict>
        <key>Crashed</key><true/>
        <key>SuccessfulExit</key><false/>
    </dict>

    <key>RunAtLoad</key>
    <true/>

    <key>StandardOutPath</key>
    <string>{gw_log}</string>
    <key>StandardErrorPath</key>
    <string>{gw_log}</string>

    <key>ThrottleInterval</key>
    <integer>5</integer>
</dict>
</plist>
"""


def _is_loaded(label: str) -> bool:
    r = subprocess.run(
        ["launchctl", "print", f"gui/{os.getuid()}/{label}"],
        capture_output=True, text=True, check=False,
    )
    return r.returncode == 0


def _retire_old() -> None:
    """Boot out + delete any superseded gateway LaunchAgent (e.g. the old
    `app.beacon.hermes-gateway`) so :8643 isn't double-bound."""
    for old in _OLD_LABELS:
        p = _PLIST_DIR / f"{old}.plist"
        loaded = _is_loaded(old)
        if not p.exists() and not loaded:
            continue
        subprocess.run(["launchctl", "bootout", f"gui/{os.getuid()}/{old}"], check=False)
        p.unlink(missing_ok=True)
        log.info("retired old gateway LaunchAgent %s", old)


def install() -> None:
    """Install + load the gateway LaunchAgent **iff** this machine has Hermes; otherwise
    ensure no gateway agent lingers. Idempotent, safe on every startup. Never raises out
    (a gateway problem must not block the bridge)."""
    try:
        _retire_old()
        if not hermes_available():
            uninstall()          # Hermes absent/removed → don't keep a dead gateway agent
            return
        script = _launch_gateway_script()
        if script is None:
            log.warning("hermes gateway: launch_gateway.py not found — skipping")
            return
        _PLIST_DIR.mkdir(parents=True, exist_ok=True)
        desired = _plist_content(script)
        if not _PLIST_PATH.exists() or _PLIST_PATH.read_text() != desired:
            _PLIST_PATH.write_text(desired)
            log.info("Hermes gateway LaunchAgent plist written at %s", _PLIST_PATH)
        if not _is_loaded(_LABEL):
            subprocess.run(["launchctl", "load", str(_PLIST_PATH)], check=False)
            log.info("Hermes gateway LaunchAgent loaded — gateway will run on :8643")
    except Exception:
        log.warning("hermes gateway install failed (non-fatal)", exc_info=True)


def uninstall() -> None:
    """Boot out + remove our gateway LaunchAgent (no-op if not installed)."""
    if not _PLIST_PATH.exists() and not _is_loaded(_LABEL):
        return
    subprocess.run(["launchctl", "bootout", f"gui/{os.getuid()}/{_LABEL}"], check=False)
    _PLIST_PATH.unlink(missing_ok=True)
    log.info("Hermes gateway LaunchAgent removed")
