#!/usr/bin/env python3
"""Drive live mugi_backend via HTTP: multi-turn prompts intended to invoke tools."""
from __future__ import annotations

import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request


def _pgrep_backend_pids() -> list[int]:
    # macOS pgrep: pattern must not start with "-" (parsed as flags).
    try:
        out = subprocess.check_output(
            ["pgrep", "-f", "mugi_backend"],
            text=True,
        ).strip()
    except subprocess.CalledProcessError:
        return []
    pids: list[int] = []
    for line in out.splitlines():
        s = line.strip()
        if s.isdigit():
            pids.append(int(s))
    return pids


def _pick_http_backend_pid() -> int | None:
    """Prefer a `mugi_backend` process that is listening on TCP (HTTP server)."""
    for pid in _pgrep_backend_pids():
        if _port_from_pid(pid) is not None:
            return pid
    return None


def _auth_from_pid(pid: int) -> str:
    out = subprocess.check_output(["ps", "eww", "-p", str(pid)], text=True)
    for tok in out.split():
        if tok.startswith("MUGI_AUTH_TOKEN="):
            return tok.split("=", 1)[1]
    raise SystemExit(f"No MUGI_AUTH_TOKEN in environment for pid {pid}")


def _port_from_pid(pid: int) -> int | None:
    try:
        out = subprocess.check_output(
            ["lsof", "-nP", "-p", str(pid)],
            text=True,
            stderr=subprocess.DEVNULL,
        )
    except subprocess.CalledProcessError:
        return None
    for line in out.splitlines():
        if "TCP" in line and "LISTEN" in line:
            # e.g. *:62225 (LISTEN)
            part = line.split()[-2]  # *:62225
            if ":" in part:
                try:
                    return int(part.rsplit(":", 1)[-1])
                except ValueError:
                    continue
    return None


def http_json(method: str, url: str, headers: dict, body: dict | None = None) -> tuple[int, object]:
    data = None
    hdrs = {k: v for k, v in headers.items()}
    if body is not None:
        data = json.dumps(body).encode("utf-8")
        hdrs.setdefault("Content-Type", "application/json")
    req = urllib.request.Request(url, data=data, headers=hdrs, method=method)
    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            raw = resp.read().decode("utf-8", errors="replace")
            code = resp.getcode()
    except urllib.error.HTTPError as e:
        raw = e.read().decode("utf-8", errors="replace")
        code = e.code
    try:
        parsed = json.loads(raw) if raw.strip() else {}
    except json.JSONDecodeError:
        parsed = raw
    return code, parsed


def main() -> int:
    pid = _pick_http_backend_pid()
    if not pid:
        print("No listening mugi_backend pid found (pgrep -f mugi_backend)", file=sys.stderr)
        return 2
    port = _port_from_pid(pid)
    if not port:
        print(f"No LISTEN port for backend pid {pid}", file=sys.stderr)
        return 2
    token = _auth_from_pid(pid)
    base = f"http://127.0.0.1:{port}"
    h = {"X-Auth-Token": token}

    code, conv = http_json("POST", f"{base}/conversations/new", h)
    if code != 200 or not isinstance(conv, dict) or "thread_id" not in conv:
        print("new conversation failed", code, conv, file=sys.stderr)
        return 1
    tid = conv["thread_id"]
    print(f"backend pid={pid} port={port} thread_id={tid}\n")

    prompts = [
        "Call get_current_directory once. Reply with one line: CWD=<path>",
        (
            "Call search_filesystem once: search for files matching '*.toml' "
            "with a reasonable result limit (e.g. 25). Report count and up to 5 basenames."
        ),
        "Call get_clipboard_text once. If empty, reply CLIPBOARD_EMPTY. Else first 80 chars only.",
        (
            "Call search_web with query 'Python 3.12 release' and max_results 3. "
            "Reply with only the result titles (or SEARCH_FAILED plus one short reason)."
        ),
    ]

    for i, text in enumerate(prompts, start=1):
        print(f"======== turn {i} ========")
        print("USER:", text[:120] + ("…" if len(text) > 120 else ""))
        code, send_ack = http_json(
            "POST",
            f"{base}/send",
            h,
            {"text": text, "thread_id": tid},
        )
        print("POST /send", code, send_ack)
        if code != 200:
            continue

        last_n = 0
        for _ in range(90):
            time.sleep(2)
            c2, msgs = http_json("GET", f"{base}/chat/messages?thread_id={tid}", h)
            if c2 != 200 or not isinstance(msgs, list):
                print("  poll failed", c2, msgs)
                continue
            if len(msgs) != last_n:
                print(f"  ... messages={len(msgs)} last_role={msgs[-1].get('role')}")
                last_n = len(msgs)
            if msgs and msgs[-1].get("role") == "assistant":
                break
        else:
            print("  TIMEOUT waiting for assistant")

        print("--- transcript (last 6) ---")
        for m in msgs[-6:]:  # type: ignore[name-defined]
            role = m.get("role", "")
            content = m.get("content") or ""
            if len(content) > 500:
                content = content[:500] + "…"
            content = content.replace("\n", " ")
            print(f"  {role:10} {content}")
        print()

    c, msgs = http_json("GET", f"{base}/chat/messages?thread_id={tid}", h)
    if isinstance(msgs, list):
        from collections import Counter

        print("role counts:", dict(Counter(m.get("role", "") for m in msgs)))

    if os.environ.get("TOOL_PROBE_DELETE", "1") == "1":
        http_json("DELETE", f"{base}/conversations/{tid}", h)
        print("deleted probe thread (TOOL_PROBE_DELETE=0 to keep)")

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
