import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { DatabaseSync } from "node:sqlite";
import { execFileSync } from "node:child_process";
import { join } from "node:path";
import { expect } from "e2e";
import { APP_DIR, T3_HOME } from "../e2e.config.ts";
import { test, WEB } from "./t3.ts";

test("Identified ACP plans replace their original rows across turns", WEB, async ({ t3, app, screen }) => {
  // Only the free replay agent's input is changed; both apps run their own unmodified adapter.
  const agent = join(APP_DIR, "apps/server/scripts/dev-replay-agent.ts");
  writeFileSync(agent, readFileSync(agent, "utf8").replace("protocolVersion: 1,", "protocolVersion: 2,"));
  const entries: unknown[] = [];
  const prompt = () => entries.push({ type: "expect_outbound", frame: { kind: "request", method: "session/prompt" } });
  const update = (update: unknown) => entries.push({ type: "emit_inbound", frame: { kind: "notification", method: "session/update", params: { sessionId: "fixture", update } } });
  const plan = (planId: string, content: string) => update({ sessionUpdate: "plan_update", plan: { type: "items", planId, entries: [{ content, priority: "medium", status: "pending" }] } });
  const legacy = (content: string) => update({ sessionUpdate: "plan", entries: [{ content, priority: "medium", status: "pending" }] });
  const done = (text: string) => {
    update({ sessionUpdate: "agent_message_chunk", content: { type: "text", text } });
    entries.push({ type: "emit_inbound", frame: { kind: "response", method: "session/prompt", result: { stopReason: "end_turn" } } });
  };
  prompt();
  plan("remove-me", "Remove this identified task");
  plan("replace-me", "Original identified task");
  legacy("Legacy first-turn task");
  done("First turn complete.");
  prompt();
  update({ sessionUpdate: "plan_removed", planId: "remove-me" });
  plan("replace-me", "Replacement identified task");
  legacy("Legacy second-turn task");
  done("Second turn complete.");
  writeFileSync(join(APP_DIR, "apps/server/scripts/dev-replay/showcase_transcript.ndjson"), entries.map(e => JSON.stringify(e)).join("\n") + "\n");
  const project = await t3.project("ACP plan demo");
  const thread = await t3.thread({ project, title: "Cross-turn plans", model: "showcase", messages: ["Create two identified plans and one anonymous plan."] });
  const snapshot = () => {
    const db = new DatabaseSync(join(T3_HOME, "userdata/statev2.sqlite"), { readOnly: true });
    try {
      return db.prepare("SELECT payload_json FROM orchestration_v2_projection_plans WHERE thread_id = ? ORDER BY plan_id").all(thread.threadId).map(row => JSON.parse(String(row.payload_json)));
    } finally { db.close(); }
  };
  const first = snapshot();
  await t3.open(thread);
  await screen.getByText("First turn complete.", { exact: true }).waitFor({ state: "visible" });
  await screen.getByRole("button", /^Worked/).first().tap();
  await app.screenshot("first-turn");
  await screen.getByRole("textbox").fill("Remove the first identified plan; replace the second. Add an anonymous plan.");
  await screen.getByRole("textbox").press("Enter");
  await screen.getByText("Second turn complete.", { exact: true }).waitFor({ state: "visible" });
  await screen.getByRole("button", "Stop generation").waitFor({ state: "hidden" });
  await screen.getByRole("button", /^Worked/).first().tap();
  await screen.getByRole("button", /^Worked/).nth(1).tap();
  await app.screenshot("second-turn");
  const second = snapshot();
  const sha = execFileSync("git", ["rev-parse", "HEAD"], { cwd: APP_DIR, encoding: "utf8" }).trim();
  const output = join(process.env.T3_PLAN_EVIDENCE_DIR ?? tmpdir(), `acp-plans-${new Date().toISOString().replaceAll(":", "-")}`);
  mkdirSync(output, { recursive: true });
  writeFileSync(join(output, `artifacts-${sha.slice(0, 10)}.json`), JSON.stringify({ sha, first, second }, null, 2));
  await expect(screen.getByText("Tasks 0/1 · Original identified task", { exact: true })).toHaveCount(0);
  await expect(screen.getByText("Tasks 0/1 · Replacement identified task", { exact: true })).toHaveCount(1);
  await expect(screen.getByText("Tasks 0/1 · Legacy first-turn task", { exact: true })).toHaveCount(1);
  await expect(screen.getByText("Tasks 0/1 · Legacy second-turn task", { exact: true })).toHaveCount(1);
  const initialRemoved = first.find(p => p.steps[0]?.text === "Remove this identified task");
  const initialReplaced = first.find(p => p.steps[0]?.text === "Original identified task");
  expect(second.find(p => p.id === initialRemoved.id)?.status).toBe("superseded");
  expect(second.find(p => p.id === initialReplaced.id)?.steps[0]?.text).toBe("Replacement identified task");
  expect(second.filter(p => p.steps[0]?.text.startsWith("Legacy ")).length).toBe(2);
});
