import { expect } from "e2e";
import { chromium } from "playwright-core";
import { createServer } from "node:http";
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { test, WEB } from "./t3.ts";

test("A renderer crash keeps the Browser tab, reports the crash, and Reload recovers", WEB,
  async ({ t3, app, screen, browser }) => {
    const site = createServer((_req, res) => {
      res.setHeader("Content-Type", "text/html");
      res.end(`<!doctype html><title>Renderer crash demo</title><style>body{font:24px system-ui;padding:36px;background:#f0f7ff;color:#173050}h1{font-size:32px}</style><h1>Renderer crash demo</h1><p>This page is running.</p><p>After a renderer crash, Reload should return to this URL.</p>`);
    });
    await new Promise<void>(resolve => site.listen(8765, "127.0.0.1", resolve));
    try {
      const project = await t3.project("Browser crash proof");
      const thread = await t3.thread({project, title: "Renderer crash recovery", messages: ["Ready"]});
      const credential = JSON.parse(await readFile(process.env.ISSUE21022_CREDENTIAL_FILE!, "utf8"));
      let mcpSession: string | null = null;
      let sequence = 0;
      const rpc = async (method: string, params: unknown, notification = false) => {
        const response = await fetch(credential.endpoint, {
          method: "POST",
          headers: {"content-type": "application/json", accept: "application/json, text/event-stream", authorization: credential.authorization,
            ...(mcpSession ? {"mcp-session-id": mcpSession, "mcp-protocol-version": "2025-06-18"} : {})},
          body: JSON.stringify({jsonrpc: "2.0", ...(notification ? {} : {id: ++sequence}), method, params}),
        });
        mcpSession = response.headers.get("mcp-session-id") ?? mcpSession;
        const text = await response.text();
        if (!response.ok) throw new Error(`MCP returned HTTP ${response.status}`);
        if (!text) return null;
        const payloads = text.startsWith("data:") || text.includes("\ndata:")
          ? text.split("\n").filter(line => line.startsWith("data:")).map(line => JSON.parse(line.slice(5)))
          : [JSON.parse(text)];
        return payloads.find(entry => entry.id === sequence)?.result;
      };
      await rpc("initialize", {protocolVersion: "2025-06-18", capabilities: {}, clientInfo: {name: "crash-evidence", version: "1"}});
      await rpc("notifications/initialized", {}, true);
      const status = (tabId?: string) => rpc("tools/call", {name: "preview_status", arguments: tabId ? {tabId} : {}});
      const transcript: Record<string, unknown> = {};
      const evidenceDir = `${process.env.ISSUE21022_OUTPUT}/${Date.now()}`;
      await mkdir(evidenceDir, {recursive: true});
      const saveTranscript = () => writeFile(`${evidenceDir}/agent-preview-status.json`, JSON.stringify(transcript, null, 2));
      await browser.addInitScript(() => {
        const closures: Array<{code: number; reason: string}> = [];
        Object.assign(window, {crashEvidenceStreamClosures: closures});
        const OriginalWebSocket = window.WebSocket;
        window.WebSocket = class extends OriginalWebSocket {
          constructor(url: string | URL, protocols?: string | string[]) {
            super(url, protocols);
            if (String(url).includes("/api/preview-stream")) {
              this.addEventListener("close", event => closures.push({code: event.code, reason: event.reason}));
            }
          }
        };
      });
      await t3.open(thread);
      await screen.getByRole("button", "Toggle right panel").tap();
      await screen.getByRole("button", "Add panel surface").tap();
      await screen.getByRole("menuitem", "Browser B", {exact: true}).tap();
      const address = screen.getByPlaceholder("Search or enter URL");
      await address.fill("http://127.0.0.1:8765/crash-demo.html");
      await browser.keyboard.press("Enter");
      const remote = await chromium.connectOverCDP("http://127.0.0.1:19222");
      try {
        const page = remote.contexts().flatMap(context => context.pages())[0];
        if (!page) throw new Error("The server browser did not navigate to the demo page");
        await page.waitForURL("http://127.0.0.1:8765/crash-demo.html");
        await page.waitForLoadState("load");
        await app.screenshot("01-page-running");
        const before = await status();
        transcript.beforeCrash = before;
        const beforeState = before?.structuredContent;
        const tabId = beforeState?.tabId ?? beforeState?.tabs?.[0]?.tabId;
        if (!tabId) throw new Error("The agent could not find the demo tab before the crash");
        const cdp = await page.context().newCDPSession(page);
        const crashed = page.waitForEvent("crash");
        void cdp.send("Page.crash").catch(() => {});
        await crashed;
        // Give the real app time to receive and render the crash event; this is evidence capture.
        await new Promise(resolve => setTimeout(resolve, 2000));
        await app.screenshot("02-after-renderer-crash");
        const after = await status(tabId);
        transcript.afterCrash = after;
        transcript.viewerStreamClosures = await browser.evaluate("window.crashEvidenceStreamClosures");
        await saveTranscript();
        await expect(screen.getByText("Page crashed", {exact: true})).toBeVisible();
        await expect(after?.structuredContent?.tabId).toBe(tabId);
        await expect(after?.structuredContent?.loadError?.description).toBe("Page crashed");
        await expect(address).toBeVisible();
        await expect(screen.getByRole("button", "Reload", {exact: true})).toBeVisible();
        await screen.getByRole("button", "Reload", {exact: true}).tap();
        const recovered = remote.contexts().flatMap(context => context.pages()).find(page => page.url().includes("8765"));
        if (!recovered) throw new Error("Reload did not retain the crashed URL");
        await recovered.waitForLoadState("load");
        await new Promise(resolve => setTimeout(resolve, 1500));
        await app.screenshot("03-reloaded-page");
        transcript.afterReload = await status(tabId);
        await saveTranscript();
        await expect(transcript.afterReload.structuredContent?.url).toBe("http://127.0.0.1:8765/crash-demo.html");
        await expect(screen.getByText("Page crashed", {exact: true})).not.toBeVisible();
      } finally { await remote.close(); }
    } finally { await new Promise<void>(resolve => site.close(() => resolve())); }
  });
