import { readFile, writeFile, mkdir } from "node:fs/promises";
import { expect } from "e2e";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { setTimeout } from "node:timers/promises";
import { APP_DIR, T3_HOME } from "../e2e.config.ts";
import { test, WEB } from "./t3.ts";

// Controlled ACP emissions; the same replay fixture is installed on each app revision.
async function installRecording() {
  const path = `${APP_DIR}/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/registry_transcript.ndjson`;
  const original = await readFile(path, "utf8");
  const agentPath = `${APP_DIR}/apps/server/scripts/dev-replay-agent.ts`;
  const originalAgent = await readFile(agentPath, "utf8");
  const prompt = { type: "expect_outbound", frame: { kind: "request", method: "session/prompt" } };
  const done = { type: "emit_inbound", frame: { kind: "response", method: "session/prompt", result: { stopReason: "end_turn" } } };
  const update = (update: unknown) => ({ type: "emit_inbound", frame: { kind: "notification", method: "session/update", params: { sessionId: "replay", update } } });
  const plan = (planId: string, content: string) => update({ sessionUpdate: "plan_update", plan: { type: "items", planId, entries: [{ content, priority: "medium", status: "pending" }] } });
  const message = (text: string) => update({ sessionUpdate: "agent_message_chunk", content: { type: "text", text } });

    const recording = [prompt, plan("remove-me", "Remove this identified task"), plan("replace-me", "Original identified task"), message("First turn complete."), done, prompt, update({ sessionUpdate: "plan_removed", planId: "remove-me" }), plan("replace-me", "Replacement identified task"), message("Second turn complete."), done].map(x => JSON.stringify(x)).join("\n");
    if (original !== recording) await writeFile(path, recording);
    if (originalAgent.includes("protocolVersion: 1,")) await writeFile(agentPath, originalAgent.replace("protocolVersion: 1,", "protocolVersion: 2,"));
}

await installRecording();

test("Identified plans are replaced and removed across two turns", WEB, async ({ t3, app, screen }) => {
    const project = await t3.project("ACP plans");
    const thread = await t3.thread({ project, title: "Cross-turn identified plans", model: "multi-turn", messages: ["Create two identified plans."] });
    await t3.open(thread);
    await screen.getByText("First turn complete.", { exact: true }).waitFor();
    await screen.getByRole("button", /^Worked for/).first().tap();
    const snapshots: Record<string, Array<{ plan_id: string; status: string; payload_json: string }>> = {};
    const record = async (phase: string) => {
      const { stdout } = await promisify(execFile)("sqlite3", ["-readonly", "-json", `${T3_HOME}/userdata/statev2.sqlite`, "select plan_id,status,payload_json from orchestration_v2_projection_plans order by plan_id"]);
      snapshots[phase] = JSON.parse(stdout);
      const { stdout: sha } = await promisify(execFile)("git", ["-C", APP_DIR, "rev-parse", "HEAD"]);
      const out = process.env.T3_PROOF_SCRATCH;
      if (out) {
        await mkdir(out, { recursive: true });
        await writeFile(`${out}/${sha.trim().slice(0,10)}-artifacts.json`, JSON.stringify(snapshots, null, 2));
      }
    };
    const capture = async (label: string) => {
      await screen.getByText("Syncing messages", { exact: false }).waitFor({ state: "hidden" });
      await setTimeout(1000);
      let before = await app.screenshot(`${label}-stability`);
      for (let attempt = 0; attempt < 5; attempt++) {
        await setTimeout(500);
        const after = await app.screenshot(attempt === 0 ? label : `${label}-settled-${attempt}`);
        if ((await readFile(before)).equals(await readFile(after))) return;
        before = after;
      }
      throw new Error(`Capture ${label} was not stable`);
    };
    await record("firstTurn");
    await capture("first-turn");
    await screen.getByRole("textbox").fill("Remove the first plan and replace the second plan in this same session.");
    await screen.getByRole("textbox").press("Enter");
    await screen.getByText("Second turn complete.", { exact: true }).waitFor();
    await screen.getByRole("button", "Stop generation").waitFor({ state: "hidden" });
    await screen.getByText("Syncing messages", { exact: false }).waitFor({ state: "hidden" });
    await setTimeout(1000);
    for (const group of await screen.getByRole("button", /^Worked for/).all()) {
      if (await group.getAttribute("aria-expanded") === "false") await group.tap();
    }
    await record("secondTurn");
    await capture("second-turn");
    expect(snapshots.secondTurn.map(x => x.plan_id).sort()).toEqual(snapshots.firstTurn.map(x => x.plan_id).sort());
    const removedId = snapshots.firstTurn.find(x => x.payload_json.includes("Remove this identified task"))!.plan_id;
    expect(snapshots.secondTurn.find(x => x.plan_id === removedId)!.status).toBe("superseded");
    const replacedId = snapshots.firstTurn.find(x => x.payload_json.includes("Original identified task"))!.plan_id;
    expect(JSON.parse(snapshots.secondTurn.find(x => x.plan_id === replacedId)!.payload_json).steps[0].text).toBe("Replacement identified task");
    // Historical row in turn one remains; removal must not add a pending-looking row in turn two.
    await expect(screen.getByRole("button", "Tasks 0/1 · Remove this identified task", { exact: true })).toHaveCount(1);
});
