# -*- coding: utf-8 -*-
"""task-2945 보조 harness — `cokacdir --cron` 직접 발사로 순수 spawn 지연 측정.

dispatch() 경로(프롬프트 ~6.8KB)는 전건 silent-drop 되어 marker 가 영원히 오지
않았다. 따라서 "봇 기동 지연" 자체를 재려면 드롭되지 않는 **최소 프롬프트**로
cron 을 직접 발사해야 한다.

측정 앵커
  t0       : `cokacdir --cron` 호출 직전
  t_sched  : cron 등록 응답 수신 (= dispatch 가 검증을 시작하는 시점의 대용)
  t_marker : spawn-confirmed marker 최초 목격

--pad-bytes 로 프롬프트를 인위적으로 부풀려 drop 임계를 브래킷할 수 있다.
"""
from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
import threading
import time
from datetime import datetime
from pathlib import Path

WORKSPACE = Path("/home/jay/workspace")
EVENTS_DIR = WORKSPACE / "memory" / "events"
CHAT_ID = "6937032012"
BOTS = ["dev3", "dev4", "dev5", "dev7", "dev8"]

sys.path.insert(0, str(WORKSPACE / "scripts" / "measure"))
from task2945_spawn_latency import MarkerWatcher  # noqa: E402


def build_prompt(task_id: str, bot: str, pad_bytes: int = 0) -> str:
    p = (
        f"[측정용] 아래 명령 1개만 즉시 실행하고 바로 종료하세요. 다른 작업·보고·파일생성 금지.\n"
        f"python3 {WORKSPACE}/dispatch/spawn_verification.py --task-id {task_id} --bot-id {bot}\n"
    )
    if pad_bytes > 0:
        pad = "\n# padding(무시): " + ("가" * max(0, pad_bytes // 3))
        p += pad
    return p


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--probes", type=int, default=6)
    ap.add_argument("--start-id", type=int, default=99031)
    ap.add_argument("--delay", type=int, default=10, help="--at now+delay 초")
    ap.add_argument("--pad-bytes", type=int, default=0)
    ap.add_argument("--bot-offset", type=int, default=0, help="BOTS 로테이션 시작 인덱스")
    ap.add_argument("--spacing", type=int, default=45, help="probe 간 간격 초")
    ap.add_argument("--grace", type=int, default=180)
    ap.add_argument("--out", default=str(WORKSPACE / "memory" / "reports" / "task-2945-direct.jsonl"))
    args = ap.parse_args()

    keys = {}
    env_txt = (WORKSPACE / ".env.keys").read_text(encoding="utf-8")
    for line in env_txt.splitlines():
        line = line.strip()
        if line.startswith("export "):
            line = line[len("export "):].strip()
        if line.startswith("COKACDIR_KEY_DEV"):
            k, _, v = line.partition("=")
            keys[k.replace("COKACDIR_KEY_", "").lower()] = v.strip().strip('"').strip("'")

    watcher = MarkerWatcher(EVENTS_DIR)
    watcher.snapshot_baseline()
    watcher.start()
    print(f"[direct] watcher 시작 (baseline {len(watcher.baseline)})", flush=True)

    records = []
    for i in range(args.probes):
        bot = BOTS[(i + args.bot_offset) % len(BOTS)]
        task_id = f"task-{args.start_id + i}"
        prompt = build_prompt(task_id, bot, args.pad_bytes)
        at = datetime.fromtimestamp(time.time() + args.delay).strftime("%Y-%m-%d %H:%M:%S")
        cmd = ["cokacdir", "--cron", prompt, "--at", at, "--chat", CHAT_ID,
               "--key", keys[bot], "--once"]
        print(f"\n[probe {i+1}/{args.probes}] {task_id} → {bot}  prompt={len(prompt.encode())}B "
              f"at={at} ({datetime.now():%H:%M:%S})", flush=True)
        t0 = time.time()
        try:
            cp = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
            out = cp.stdout.strip().splitlines()[-1] if cp.stdout.strip() else ""
            resp = json.loads(out) if out.startswith("{") else {"raw": out}
        except Exception as exc:
            resp = {"status": "harness_error", "message": repr(exc)}
        t1 = time.time()
        rec = {
            "probe": i + 1, "task_id": task_id, "bot": bot,
            "prompt_bytes": len(prompt.encode()), "pad_bytes": args.pad_bytes,
            "t0": t0, "t0_iso": datetime.fromtimestamp(t0).isoformat(),
            "at": at, "reg_elapsed": round(t1 - t0, 3),
            "cron_status": resp.get("status"), "schedule_id": resp.get("id"),
        }
        records.append(rec)
        print(f"    cron 등록: status={rec['cron_status']} id={rec['schedule_id']} "
              f"({rec['reg_elapsed']}s)", flush=True)
        if i < args.probes - 1:
            time.sleep(args.spacing)

    print(f"\n[direct] grace {args.grace}s 관측...", flush=True)
    deadline = time.time() + args.grace
    while time.time() < deadline:
        if all(watcher.find(r["task_id"]) for r in records):
            print("[direct] 전건 marker 확보 — 조기 종료", flush=True)
            break
        time.sleep(1)
    watcher.stop()

    for rec in records:
        m = watcher.find(rec["task_id"])
        if m:
            rec["t_marker"] = m["first_seen"]
            rec["t_marker_iso"] = datetime.fromtimestamp(m["first_seen"]).isoformat()
            rec["latency_from_call"] = round(m["first_seen"] - rec["t0"], 3)
            rec["marker_payload"] = m["payload"]
        else:
            rec["latency_from_call"] = None

    out_p = Path(args.out)
    out_p.parent.mkdir(parents=True, exist_ok=True)
    with out_p.open("a", encoding="utf-8") as f:
        for rec in records:
            f.write(json.dumps(rec, ensure_ascii=False) + "\n")

    lat = [r["latency_from_call"] for r in records if r.get("latency_from_call") is not None]
    print(f"\n[direct] marker {len(lat)}/{len(records)}건  →  {sorted(lat)}", flush=True)
    print(f"[direct] raw: {out_p}", flush=True)
    return 0


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