"""Drive a real `codex app-server` the way CodexAdapterV2 does around compaction (#25891).

usage: driver.py <codex_home> <inject:0|1> <seconds>
With inject=1, every item/completed contextCompaction is answered with thread/inject_items
carrying the turn's additionalContext, as restoreAdditionalContext does.
"""

import json
import subprocess
import sys
import threading
import time

home, mode, seconds = sys.argv[1], sys.argv[2], float(sys.argv[3])
inject = mode == "1"
deferred = mode == "deferred"
awaiting = set()
INJECT_DELAY = float(sys.argv[4]) if len(sys.argv) > 4 else 0
proc = subprocess.Popen(["codex", "app-server"], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                        stderr=open(f"{home}/app-server.stderr", "w"), env={"CODEX_HOME": home, "PATH": "/usr/local/nvm/versions/node/v24.21.0/bin:/usr/bin:/bin", "HOME": home},
                        text=True, bufsize=1)
next_id = [0]
pending = {}
events = []
done = threading.Event()
second = threading.Event()
CONTEXT = {"t3_code": {"kind": "application", "value": "T3_REPRO_CONTEXT you are a T3 delegated reviewer"}}
thread_id = [None]
t0 = time.time()


def send(method, params):
    next_id[0] += 1
    rid = next_id[0]
    ev = threading.Event()
    pending[rid] = [ev, None]
    proc.stdin.write(json.dumps({"jsonrpc": "2.0", "id": rid, "method": method, "params": params}) + "\n")
    proc.stdin.flush()
    return rid, ev


def call(method, params):
    rid, ev = send(method, params)
    ev.wait(30)
    return pending[rid][1]


def log(kind, detail=""):
    line = f"{time.time() - t0:6.2f}s {kind} {detail}"
    events.append(line)
    print(line, flush=True)


def reader():
    for raw in proc.stdout:
        msg = json.loads(raw)
        if "id" in msg and "method" not in msg:
            if msg["id"] in pending:
                pending[msg["id"]][1] = msg
                pending[msg["id"]][0].set()
            continue
        m, p = msg.get("method"), msg.get("params", {})
        if "id" in msg:  # server request (approvals): accept
            proc.stdin.write(json.dumps({"jsonrpc": "2.0", "id": msg["id"], "result": {"decision": "accept"}}) + "\n")
            proc.stdin.flush()
            log("server-request", m)
            continue
        if m in ("turn/started", "turn/completed"):
            log(m, json.dumps({"turn": p.get("turn", {}).get("id"), "status": p.get("turn", {}).get("status")}))
            if m == "turn/completed":
                if deferred and thread_id[0] in awaiting:
                    awaiting.discard(thread_id[0])
                    threading.Thread(target=lambda: (log("inject_items (deferred) ->", json.dumps(call("thread/inject_items", {"threadId": thread_id[0], "items": [{"type": "message", "role": "developer", "content": [{"type": "input_text", "text": f"<{k}>{v['value']}</{k}>"}]} for k, v in CONTEXT.items()]}))[:120]), second.set()), daemon=True).start()
                done.set()
        elif m in ("item/started", "item/completed"):
            item = p.get("item", {})
            text = item.get("text", "")[:40] if item.get("type") == "agentMessage" else ""
            log(m, f"{item.get('type')} {text}")
            if m == "item/completed" and item.get("type") == "contextCompaction" and deferred:
                awaiting.add(thread_id[0])
            if m == "item/completed" and item.get("type") == "contextCompaction" and inject:
                threading.Thread(target=lambda: time.sleep(INJECT_DELAY) or log("inject_items ->", json.dumps(call("thread/inject_items", {
                    "threadId": thread_id[0],
                    "items": [{"type": "message", "role": "developer", "content": [
                        {"type": "input_text", "text": f"<{k}>{v['value']}</{k}>"}]} for k, v in CONTEXT.items()],
                }))[:120]), daemon=True).start()


threading.Thread(target=reader, daemon=True).start()
print(json.dumps(call("initialize", {"clientInfo": {"name": "t3code-repro", "version": "0"},
                                      "capabilities": {"experimentalApi": True}}))[:200])
proc.stdin.write(json.dumps({"jsonrpc": "2.0", "method": "initialized"}) + "\n")
proc.stdin.flush()
r = call("thread/start", {"cwd": home, "approvalPolicy": "never", "sandbox": "danger-full-access"})
thread_id[0] = r["result"]["thread"]["id"]
r = call("turn/start", {"threadId": thread_id[0], "input": [{"type": "text", "text": "Review the diff."}],
                         "additionalContext": CONTEXT})
log("turn/start ->", json.dumps(r)[:160])
ended = done.wait(seconds)
if deferred and ended:
    second.wait(10)
    done.clear()
    r = call("turn/start", {"threadId": thread_id[0], "input": [{"type": "text", "text": "Next turn."}], "additionalContext": CONTEXT})
    log("turn/start (2) ->", json.dumps(r)[:100])
    ended = done.wait(seconds)
compactions = sum(1 for e in events if "item/completed contextCompaction" in e)
answers = sum(1 for e in events if "agentMessage VERDICT" in e)
print(f"RESULT mode={mode} turnCompleted={ended} compactions={compactions} finalAnswers={answers} elapsed={time.time() - t0:.1f}s")
proc.kill()
