"""Command client for the persistent MCSJ JAB daemon.

Examples:
  python C:\MCSJAgent\mcsj-jab-client.py state
  python C:\MCSJAgent\mcsj-jab-client.py refresh --max-nodes 300
  python C:\MCSJAgent\mcsj-jab-client.py find "P.O. Listing"
  python C:\MCSJAgent\mcsj-jab-client.py click "P.O. Listing"
"""
import argparse, json, os, subprocess, sys, time, urllib.parse, urllib.request
import http.client, urllib.error

BASE = "http://127.0.0.1:8765"
DAEMON_PY = r"C:\MCSJAgent\mcsj-jab-daemon.py"
PY32 = r"C:\MCSJAgent\Python311-32-embed\python.exe"


def _kill_port_8765():
    """Best-effort cleanup of a stale daemon that accepts then disconnects."""
    try:
        out = subprocess.check_output("netstat -ano | findstr :8765", shell=True, text=True, stderr=subprocess.DEVNULL)
    except Exception:
        return
    pids = set()
    for line in out.splitlines():
        parts = line.split()
        if len(parts) >= 5 and parts[1].endswith(":8765") and parts[3].upper() == "LISTENING":
            pids.add(parts[-1])
    for pid in pids:
        subprocess.run(f"taskkill /PID {pid} /F", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def _start_daemon():
    creationflags = 0
    if hasattr(subprocess, "CREATE_NEW_PROCESS_GROUP"):
        creationflags |= subprocess.CREATE_NEW_PROCESS_GROUP
    if hasattr(subprocess, "DETACHED_PROCESS"):
        creationflags |= subprocess.DETACHED_PROCESS
    subprocess.Popen([PY32, DAEMON_PY], cwd=r"C:\MCSJAgent", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, creationflags=creationflags)
    time.sleep(2)


def _recover_daemon():
    _kill_port_8765()
    time.sleep(0.5)
    _start_daemon()


def req(method, path, payload=None, _retried=False):
    data = None
    headers = {}
    if payload is not None:
        data = json.dumps(payload).encode("utf-8")
        headers["Content-Type"] = "application/json"
    r = urllib.request.Request(BASE + path, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(r, timeout=120) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except (ConnectionRefusedError, ConnectionResetError, TimeoutError, http.client.RemoteDisconnected, urllib.error.URLError):
        if _retried:
            raise
        _recover_daemon()
        return req(method, path, payload, _retried=True)

def print_hits(obj):
    hits = obj.get("hits") or obj.get("rows") or []
    if "hits" in obj:
        print(f"ok={obj.get('ok')} query={obj.get('query')!r} count={obj.get('count')}")
    else:
        print(json.dumps(obj, indent=2))
        return
    for r in hits[:80]:
        b = r.get("bounds") or {}
        print(f"{r.get('id'):04d} depth={r.get('depth')} role={r.get('role')!r} name={r.get('name')!r} states={r.get('states')} bounds=({b.get('x')},{b.get('y')},{b.get('width')},{b.get('height')})")

def main():
    ap = argparse.ArgumentParser()
    sub = ap.add_subparsers(dest="cmd", required=True)
    sub.add_parser("health")
    sub.add_parser("state")
    p = sub.add_parser("refresh"); p.add_argument("--max-depth", type=int, default=10); p.add_argument("--max-nodes", type=int, default=1500)
    p = sub.add_parser("find"); p.add_argument("text"); p.add_argument("--json", action="store_true")
    p = sub.add_parser("click"); p.add_argument("text")
    p = sub.add_parser("click-id"); p.add_argument("id", type=int)
    p = sub.add_parser("set"); p.add_argument("text"); p.add_argument("value")
    p = sub.add_parser("set-id"); p.add_argument("id", type=int); p.add_argument("value")
    args = ap.parse_args()
    if args.cmd == "health": out = req("GET", "/health")
    elif args.cmd == "state": out = req("GET", "/state")
    elif args.cmd == "refresh": out = req("POST", "/refresh", {"max_depth": args.max_depth, "max_nodes": args.max_nodes})
    elif args.cmd == "find":
        out = req("GET", "/find?text=" + urllib.parse.quote(args.text))
        if not args.json:
            print_hits(out); return
    elif args.cmd == "click": out = req("POST", "/click", {"text": args.text})
    elif args.cmd == "click-id": out = req("POST", "/click", {"id": args.id})
    elif args.cmd == "set": out = req("POST", "/set", {"text": args.text, "value": args.value})
    elif args.cmd == "set-id": out = req("POST", "/set", {"id": args.id, "value": args.value})
    print(json.dumps(out, indent=2))

if __name__ == "__main__":
    main()
