import { execFileSync } from "node:child_process";
import { expect } from "e2e";
import { T3_HOME } from "../e2e.config.ts";
import { test, WEB } from "./t3.ts";

test("Opening a thread three times stores only read state", WEB, async ({ t3, app, screen, browser }) => {
  const project = await t3.project("Read receipts");
  const thread = await t3.thread({ project, title: "Visit payload evidence", messages: ["Show a short replay response"] });
  const read = () => JSON.parse(execFileSync("sqlite3", ["-readonly", "-json", `${T3_HOME}/userdata/statev2.sqlite`, "SELECT event_type AS type, length(CAST(payload_json AS BLOB)) AS bytes, payload_json FROM orchestration_events WHERE event_type IN ('thread.visited','thread.visit-recorded') ORDER BY sequence"], { encoding: "utf8" }) || "[]");
  const baseline = read().length;
  for (let visit = 1; visit <= 3; visit++) {
    await t3.open(thread);
    await screen.getByRole("textbox").first().waitFor();
    // Wait for the real server to persist the browser's visit, before reading its payload.
    for (let attempt = 0; read().length < baseline + visit && attempt < 100; attempt++) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }
  }
  const rows = read().slice(baseline);
  const summary = rows.map((row: any, index: number) => ({ visit: index + 1, event: row.type, bytes: row.bytes, keys: Object.keys(JSON.parse(row.payload_json)).sort() }));
  const staleReply = await browser.evaluate((threadId: string) => new Promise((resolve, reject) => {
    const url = new URL("/ws?orchestrationProtocol=2", location.href); url.protocol = "ws:";
    const ws = new WebSocket(url);
    const timeout = setTimeout(() => { ws.close(); reject(new Error("Stale visit RPC did not reply")); }, 15000);
    ws.onopen = () => ws.send(JSON.stringify({ _tag: "Request", id: "900001", tag: "orchestration.dispatchCommand", payload: { type: "thread.visit", commandId: crypto.randomUUID(), threadId, visitedAt: "2020-01-01T00:00:00.000Z", opened: false }, headers: [] }));
    ws.onmessage = event => { const messages = JSON.parse(event.data); for (const message of Array.isArray(messages) ? messages : [messages]) { if (String(message.requestId) === "900001" && message._tag === "Exit") { clearTimeout(timeout); ws.close(); resolve(message); } } };
    ws.onerror = () => { clearTimeout(timeout); reject(new Error("Stale visit WebSocket failed")); };
  }), thread.threadId);
  const staleWrites = read().length - baseline - rows.length;
  console.log("STALE_VISIT_REPLY", JSON.stringify(staleReply), "additional visit events", staleWrites);
  console.log("READ_ONLY_VISIT_EVIDENCE", JSON.stringify(summary));
  await browser.evaluate((data: unknown) => {
    const panel = document.createElement("section");
    panel.style.cssText = "position:fixed;inset:75px 24px 30px 300px;z-index:99999;background:#111827;color:#f9fafb;border:2px solid #94a3b8;border-radius:12px;padding:24px;overflow:auto;font:16px/1.6 monospace";
    const title = document.createElement("h2"); title.textContent = "Read-only database inspection after 3 real app opens";
    const description = document.createElement("p"); description.textContent = "Diagnostic overlay • Chromium 1280×800 • fresh synthetic database • replay provider. Values below come from persisted orchestration events, not UI state.";
    const pre = document.createElement("pre"); pre.style.cssText = "white-space:pre-wrap;font-size:14px"; pre.textContent = (data as any).visits.map((row: any) => `Visit ${row.visit}: ${row.event} — ${row.bytes} bytes\nPayload keys: ${row.keys.join(", ")}\n`).join("\n") + `\nStale visit (2020 watermark, opened=false): ${(data as any).staleVisitAdditionalEvents} additional event(s)`;
    panel.append(title, description, pre); document.body.append(panel);
  }, { visits: summary, staleVisitAdditionalEvents: staleWrites });
  await app.screenshot("persisted-visit-payloads");
  await expect(staleWrites).toBe(0);
  await expect(rows.length >= 3).toBe(true);
  await expect(summary.every((row: any) => row.keys.every((key: string) => ["threadId", "lastVisitedAt", "opens"].includes(key)))).toBe(true);
});
