#!/usr/bin/env python3
"""session-monitor — the Session Monitor CLI.

Manage THIS container's keeper and query the fleet. The keeper is a long-running
daemon; this CLI is how you install/start/stop/inspect it. (The
/adom-remote-control skill just *guides* you to these commands — the CLI does the
work.)

  session-monitor keeper start [--token T] [--service URL]   install + supervise the keeper here
  session-monitor keeper stop                                 stop this container's keeper
  session-monitor keeper status                               local process + service reporting state
  session-monitor keeper logs [-n N]                          tail the keeper log
  session-monitor status                                      keeper-fleet health (all your containers)
  session-monitor open                                        print the dashboard URL (with your token)
  session-monitor token mint <email>                          mint a token (operator; on the service container)
  session-monitor version

Add --json to any command for a machine-readable envelope.
"""
import os, sys, json, socket, subprocess, urllib.request

HOME = os.path.expanduser("~")
KEEP_DIR = os.path.join(HOME, ".session-keeper")
CLI_HOME = os.path.join(HOME, ".session-monitor-cli")                       # where the pkg install hook drops the runtime
SKILL_DIR = os.path.join(HOME, ".claude", "skills", "adom-remote-control")  # legacy bundled location (fallback)
ENV_FILE = os.path.join(KEEP_DIR, "env")
# realpath so a symlinked command (~/.local/bin/session-monitor) resolves to its
# real install dir, where keeper.py / setup.sh live.
SELF_DIR = os.path.dirname(os.path.realpath(__file__))
DEFAULT_SERVICE = "https://session-monitor-21nypoktx513.adom.cloud"
VERSION = "0.2.0"   # keep in lockstep with package.json "version" on every publish


def _skill_file(*names):
    """Find a bundled runtime file (keeper installer / stop.sh / keeper.py) — next to
    this CLI, in the CLI home, or the legacy skill dir. Accepts alternate names (the
    keeper installer is `setup.sh` in the package, `install.sh` in the source tree)."""
    for d in (SELF_DIR, CLI_HOME, SKILL_DIR):
        for name in names:
            p = os.path.join(d, name)
            if os.path.exists(p):
                return p
    return os.path.join(CLI_HOME, names[0])


def _env():
    d = {}
    try:
        for ln in open(ENV_FILE):
            ln = ln.strip()
            if "=" in ln and not ln.startswith("#"):
                k, v = ln.split("=", 1)
                d[k] = v.strip().strip("'\"")
    except Exception:
        pass
    return d


def _service():
    return (_env().get("SM_SERVICE_URL") or os.environ.get("SM_SERVICE_URL") or DEFAULT_SERVICE).rstrip("/")


def _token():
    return _env().get("SM_TOKEN") or os.environ.get("SM_TOKEN") or ""


def _machine():
    return socket.gethostname()


def _flag(args, name):
    if name in args:
        i = args.index(name)
        if i + 1 < len(args):
            return args[i + 1]
    return None


def _pgrep(pat):
    try:
        return subprocess.run(["pgrep", "-f", pat], stdout=subprocess.DEVNULL,
                              stderr=subprocess.DEVNULL).returncode == 0
    except Exception:
        return False


def _api(path):
    req = urllib.request.Request(_service() + path,
                                 headers={"X-SM-Token": _token(), "User-Agent": "session-monitor-cli/" + VERSION})
    with urllib.request.urlopen(req, timeout=15) as r:
        return json.loads(r.read())


def _out(status, msg, data=None, hints=None):
    """AI-oriented output: a human line + Hint: lines + optional JSON envelope."""
    print(f"{status}: {msg}")
    for h in (hints or []):
        print(f"Hint: {h}")
    if "--json" in sys.argv:
        print(json.dumps({"status": status, "message": msg, "data": data or {}, "hints": hints or []}))
    return 0 if status == "OK" else 1


def cmd_keeper(args):
    sub = args[0] if args else "status"
    rest = args[1:]
    if sub == "start":
        setup = _skill_file("setup.sh", "install.sh")
        token = _flag(rest, "--token") or _token()
        service = _flag(rest, "--service") or _service()
        if not os.path.exists(setup):
            return _out("ERROR", "keeper setup script not found",
                        hints=["Reinstall the CLI: adom-wiki pkg install adom/session-monitor-cli"])
        if not token:
            return _out("ERROR", "no token given",
                        hints=["Pass --token <T> (ask whoever runs the monitor), or set SM_TOKEN"])
        subprocess.run(["bash", setup, service, token])
        return _out("OK", f"keeper started → {service}", hints=["Verify: session-monitor keeper status"])
    if sub == "stop":
        stop = _skill_file("stop.sh")
        if not os.path.exists(stop):
            return _out("ERROR", "stop script not found")
        subprocess.run(["bash", stop])
        return _out("OK", "keeper stopped on this container")
    if sub == "logs":
        n = _flag(rest, "-n") or "40"
        log = os.path.join(KEEP_DIR, "keeper.log")
        if not os.path.exists(log):
            return _out("ERROR", "no keeper log yet", hints=["Start it: session-monitor keeper start --token <T>"])
        subprocess.run(["tail", "-n", str(n), log])
        return 0
    # default: status
    daemon = _pgrep(f"{KEEP_DIR}/daemon.sh")
    keeper = _pgrep(f"{KEEP_DIR}/keeper.py")
    svc = None
    try:
        svc = _api(f"/api/keeper-status?machine={_machine()}")
    except Exception as e:
        svc = {"error": str(e)}
    line = f"local daemon={'up' if daemon else 'DOWN'} keeper={'up' if keeper else 'DOWN'}"
    if svc and not svc.get("error"):
        line += (f" | service {'reporting' if svc.get('reporting') else 'NOT reporting'}"
                 f" (last seen {svc.get('age_sec')}s ago, {svc.get('sessions', 0)} sessions)")
    running = daemon and keeper
    hints = [] if running else ["Revive it: session-monitor keeper start --token <T>"]
    return _out("OK" if running else "ERROR", line,
                data={"daemon": daemon, "keeper": keeper, "service": svc}, hints=hints)


def cmd_status(args):
    try:
        d = _api("/api/keeper-health")
    except Exception as e:
        return _out("ERROR", f"can't reach the monitor service: {e}",
                    hints=["Check your token: session-monitor open"])
    return _out("OK", f"fleet: {d['reporting']} reporting · {d['down']} down · "
                      f"{d['containers']} containers · {d['owners']} owner(s)", data=d)


def cmd_open(args):
    tok = _token()
    if not tok:
        return _out("ERROR", "no token set here", hints=["Run: session-monitor keeper start --token <T> first"])
    return _out("OK", f"{_service()}/?token={tok}",
                hints=["Open on your phone; Safari → Add to Home Screen for a PWA (mic permission sticks)."])


def cmd_token(args):
    if len(args) < 2 or args[0] != "mint":
        return _out("ERROR", "usage: session-monitor token mint <email>")
    email = args[1]
    add = os.path.join(HOME, "adom-session-monitor", "service", "add-user.sh")
    if not os.path.exists(add):
        return _out("ERROR", "token minting runs on the SERVICE container (add-user.sh not found here)",
                    hints=["SSH to the service container and run: service/add-user.sh " + email])
    subprocess.run(["bash", add, email])
    return _out("OK", f"minted a token for {email}")


def main():
    argv = [a for a in sys.argv[1:] if a != "--json"]
    cmd = argv[0] if argv else "help"
    rest = argv[1:]
    if cmd in ("version", "-v", "--version"):
        return _out("OK", "session-monitor " + VERSION)
    if cmd in ("help", "-h", "--help"):
        print(__doc__)
        return 0
    dispatch = {"keeper": cmd_keeper, "status": cmd_status, "open": cmd_open, "token": cmd_token}
    fn = dispatch.get(cmd)
    if not fn:
        return _out("ERROR", f"unknown command: {cmd}", hints=["Run: session-monitor help"])
    return fn(rest)


if __name__ == "__main__":
    sys.exit(main())
