"""Export selected real runtime snapshots, redact model metadata, and verify capture completeness.

Usage: export-evidence.py manifest-private.json
Manifest: {"F03": {"before": "<timestamped run directory>", "after": "..."}, ...}
No server/DB mutation occurs. Public files contain synthetic fixture data only.
"""
import json
import pathlib
import re
import sqlite3
import sys

SCRATCH = pathlib.Path(__file__).resolve().parent.parent
PUBLIC = SCRATCH / "public"
PUBLIC.mkdir(exist_ok=True)
MODELS = set(json.loads((SCRATCH / "model-selections-private.json").read_text()).values())
TERMINAL = {"completed", "failed", "cancelled", "interrupted"}
KEYS = {
    "run": ["id", "ordinal", "status", "userMessageId", "activeAttemptId", "delegatedCompletion", "queuePosition", "queueHeld"],
    "attempt": ["id", "runId", "attemptOrdinal", "reason", "status", "providerTurnId"],
    "task": ["id", "title", "runId", "childThreadId", "status", "completionWake", "completionDelivery"],
    "message": ["id", "runId", "role", "text", "delegatedCompletion", "createdAt"],
    "node": ["id", "runId", "kind", "status"],
}


def select(value, kind):
    return {k: value[k] for k in KEYS[kind] if k in value}


def clean(value):
    if isinstance(value, dict):
        return {k: ("<model>" if k in {"model", "modelId", "modelName", "requestedModel", "resolvedModel"} and v else clean(v)) for k, v in value.items()}
    if isinstance(value, list):
        return [clean(x) for x in value]
    if isinstance(value, str):
        for model in sorted(MODELS, key=len, reverse=True):
            value = value.replace(model, "<model>")
    return value


def summarize(state):
    runs = state["runs"]
    wakes = {m.get("runId") for m in state["messages"] if m.get("delegatedCompletion")}
    wake_runs = [r for r in runs if r["id"] in wakes]
    p = runs[0]
    cohort = p.get("delegatedCompletion")
    tasks = {t["title"]: t["completionDelivery"]["state"] for t in state["subagents"]}
    return {"runCount": len(runs), "wakeRunCount": len(wake_runs), "cohort": cohort, "tasks": tasks}


def export(fixture, side, run):
    path = pathlib.Path(run)
    original = (path / f"{fixture}-{side}.txt").read_text()
    assert "— CAPTURED\n" in original, (fixture, side, "not captured")
    for capability in path.glob("capabilities-private.json"):
        for provider in json.loads(capability.read_text())["providers"]:
            for model in provider["models"]:
                MODELS.add(model["id"])
                if model.get("label"):
                    MODELS.add(model["label"])
    header = original.split("Timestamped persisted-state snapshots:")[0]
    markers = re.findall(r"(?m)^([^\n]+) MILESTONE ([^\n]+)\n", original)
    states = [json.loads(p.read_text()) for p in sorted(path.glob("state-*-private.json"))]
    assert len(markers) == len(states), (fixture, side, len(markers), len(states))
    out = [header, "Snapshot notes: P is ordinal 1. W is the first run whose persisted input message carries delegatedCompletion ownership. Wake counts use that ownership, so ordinary queued Q is excluded. All reads use mode=ro and a read transaction. UTC timestamps and event sequences were captured live. Journal excerpts retain command IDs so restart/promotion commit transitions can be read together.", ""]
    previous = 0
    for (stamp, label), state in zip(markers, states):
        assert isinstance(state["eventSequence"], int), (fixture, side, label, "missing event sequence")
        data = {"eventSequence": state["eventSequence"], "runs": [select(r, "run") for r in state["runs"]], "attempts": [select(a, "attempt") for a in state["run_attempts"]], "tasks": [select(t, "task") for t in state["subagents"]], "messages": [select(m, "message") for m in state["messages"]], "nodes": [select(n, "node") for n in state["nodes"]]}
        wake_ids = {m.get("runId") for m in state["messages"] if m.get("delegatedCompletion")}
        wakes = [r for r in state["runs"] if r["id"] in wake_ids]
        data["wakeRunCounts"] = {"total": len(wakes), "queued": sum(r["status"] == "queued" for r in wakes), "started": sum(r["status"] != "queued" for r in wakes), "terminal": sum(r["status"] in TERMINAL for r in wakes)}
        data["completionMessageCountsByGeneration"] = {str(gen): sum(m.get("delegatedCompletion", {}).get("generation") == gen for m in state["messages"]) for gen in [1, 2]}
        events = []
        for event in state.get("events", []):
            if event["sequence"] <= previous:
                continue
            kind = next((v for k, v in [("run.", "run"), ("run-attempt.", "attempt"), ("subagent.", "task"), ("message.", "message"), ("node.", "node")] if event["event_type"].startswith(k)), None)
            if kind:
                events.append({**{k: event[k] for k in ["sequence", "command_id", "event_type", "occurred_at"]}, "payload": select(event["payload"], kind)})
        data["journalEventsSincePriorSnapshot"] = events
        out.append(stamp + " MILESTONE " + label + "\n" + json.dumps(clean(data), indent=2))
        previous = state["eventSequence"]
    final = states[-1]
    summary = summarize(final)
    failed_tools = [{"sequence": event["sequence"], "occurred_at": event["occurred_at"], **{k: event["payload"][k] for k in ["type", "toolName", "input", "output", "status"] if k in event["payload"]}} for event in final.get("events", []) if event["event_type"] == "turn-item.updated" and event["payload"].get("status") == "failed" and event["payload"].get("type") in {"dynamic_tool", "tool_call"}]
    if failed_tools:
        out.append("Incidental failed provider tool calls (included so setup differences are explicit):\n" + json.dumps(clean(failed_tools), indent=2))
    if fixture in {"F03", "F04"}:
        before_state = next(s for (_, label), s in zip(markers, states) if label.startswith("before steer"))
        assert before_state["runs"][0]["delegatedCompletion"]["nextGeneration"] == 2
        assert {t["completionDelivery"]["state"] for t in before_state["subagents"]} == {"claimed", "pending"}
        w = before_state["runs"][1]
        end_w = next(r for r in final["runs"] if r["id"] == w["id"])
        assert end_w["userMessageId"] != w["userMessageId"]
        assert [a["reason"] for a in final["run_attempts"] if a["runId"] == w["id"]] == ["initial", "steering_restart"]
        assert end_w["status"] == "completed"
        if side == "before":
            assert summary["tasks"] == {"ALPHA": "claimed", "BETA": "pending"}
            assert summary["cohort"]["delivery"]["generation"] == 1
            assert summary["cohort"]["nextGeneration"] == 2
            assert summary["wakeRunCount"] == 1
        else:
            assert summary["tasks"] == {"ALPHA": "delivered", "BETA": "delivered"}
            assert summary["cohort"]["delivery"] is None
            assert summary["cohort"]["nextGeneration"] == 3
            assert sum(m.get("delegatedCompletion", {}).get("generation") == 2 for m in final["messages"]) == 1
        if fixture == "F04":
            q = next(r for r in final["runs"] if r["ordinal"] == 3)
            assert q["status"] == "cancelled"
            assert all(a["status"] == "cancelled" for a in final["run_attempts"] if a["runId"] == q["id"])
            assert all(n["status"] == "cancelled" for n in final["nodes"] if n["runId"] == q["id"])
            summary["queueRunStatus"] = q["status"]
    elif fixture == "F06":
        i = next(i for i, (_, label) in enumerate(markers) if label.startswith("before steer"))
        a, b = states[i], states[i + 1]
        w1, w2 = a["runs"][1], b["runs"][1]
        assert w1["userMessageId"] == w2["userMessageId"]
        assert w1["activeAttemptId"] == w2["activeAttemptId"]
        assert len([x for x in b["run_attempts"] if x["runId"] == w2["id"]]) == 1
        assert a["runs"][0]["delegatedCompletion"] == b["runs"][0]["delegatedCompletion"]
        assert {t["title"]: t["completionDelivery"] for t in a["subagents"]} == {t["title"]: t["completionDelivery"] for t in b["subagents"]}
        assert summary["tasks"] == {"ALPHA": "delivered", "BETA": "delivered"}
        assert summary["cohort"]["nextGeneration"] == 3 and summary["cohort"]["delivery"] is None
        db = sqlite3.connect(f"file:{path}/home/userdata/statev2.sqlite?mode=ro", uri=True)
        db.row_factory = sqlite3.Row
        effects = [dict(row) for row in db.execute("select effect_id,command_id,effect_type,status,created_at,completed_at,payload_json from orchestration_v2_effect_outbox where thread_id=? and effect_type='provider-turn.steer'", (final["runs"][0]["threadId"],))]
        db.close()
        assert len(effects) == 1 and effects[0]["status"] == "succeeded"
        assert effects[0]["completed_at"] < final["runs"][1]["completedAt"]
        for effect in effects:
            effect["payload"] = json.loads(effect.pop("payload_json"))
        out.append("Persisted native-steer effect lifecycle, exported read-only after shutdown (timestamps are stored runtime timestamps). Successful native application precedes W's terminal completedAt=" + final["runs"][1]["completedAt"] + ":\n" + json.dumps(clean(effects), indent=2))
    elif fixture == "F07":
        assert all(not r.get("delegatedCompletion") for s in states for r in s["runs"])
        assert all(not s["subagents"] for s in states)
        assert summary["runCount"] == 1 and summary["wakeRunCount"] == 0
        assert final["runs"][0]["status"] == "completed"
        assert [a["reason"] for a in final["run_attempts"]] == ["initial", "steering_restart"]
    output = clean("\n\n".join(out)) + "\n"
    for model in MODELS:
        assert model not in output, (fixture, side, "model metadata remaining")
    target = PUBLIC / f"{fixture}-{side}.txt"
    target.write_text(output)
    print(f"{fixture} {side}: {json.dumps(summary)}; {len(states)} milestones; {len(output)} bytes")
    return summary


manifest = json.loads(pathlib.Path(sys.argv[1]).read_text())
summaries = {fixture: {side: export(fixture, side, run) for side, run in pair.items()} for fixture, pair in manifest.items()}
(PUBLIC / "capture-summary.json").write_text(json.dumps(summaries, indent=2))
