import { expect } from "e2e";
import { spawn } from "node:child_process";
import { mkdir, readFile, copyFile } from "node:fs/promises";
import { createServer } from "node:net";
import { once } from "node:events";
import { APP_DIR, T3_HOME } from "../e2e.config.ts";
import { makeState, test, WEB } from "./t3.ts";

const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

test("A reconnecting paired environment offers an immediate retry", WEB, async ({t3, app, screen, browser}) => {
  await t3.project("Local workspace");
  await t3.open();
  const origin = await browser.evaluate(() => location.origin);
  const listener = createServer();
  listener.listen(0, "127.0.0.1");
  await once(listener, "listening");
  const port = (listener.address() as {port:number}).port;
  await new Promise<void>(resolve => listener.close(() => resolve()));
  const home = `${T3_HOME}/paired`;
  await mkdir(`${home}/userdata`, {recursive:true});
  await copyFile(`${T3_HOME}/userdata/settings.json`, `${home}/userdata/settings.json`);
  const env = {...process.env, PATH:`${T3_HOME}/bin:${process.env.PATH}`, T3CODE_DEV_SEED:"0", T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD:"0", T3CODE_DEV_ALLOWED_ORIGINS:origin};
  let output = "";
  let server: ReturnType<typeof spawn> | undefined;
  function start() {
    output = "";
    server = spawn(process.execPath, ["scripts/dev-runner.ts", "dev:server", "--home-dir", home, "--no-seed", "--host", "127.0.0.1", "--port", String(port)], {cwd:APP_DIR, env, detached:true, stdio:["ignore","pipe","pipe"]});
    server.stdout?.on("data", data => { output += data.toString(); });
    server.stderr?.on("data", data => { output += data.toString(); });
  }
  async function stop() {
    if (!server?.pid) return;
    const exited = once(server, "exit");
    process.kill(-server.pid, "SIGTERM");
    await exited;
    server = undefined;
  }
  async function ready() {
    for (let n=0;n<180;n++) {
      if (output.includes("pairingUrl:")) {
        try { if ((await fetch(`http://127.0.0.1:${port}/.well-known/t3/environment`)).ok) return; } catch {}
      }
      if (server?.exitCode !== null) throw new Error("Paired server exited");
      await delay(500);
    }
    throw new Error("Paired server startup timed out");
  }
  async function capture(label: string) {
    let previous = await app.screenshot(`${label}-stability`);
    for (let n=0;n<12;n++) {
      await delay(500);
      const current = await app.screenshot(label);
      if ((await readFile(previous)).equals(await readFile(current))) return;
      previous = current;
    }
    throw new Error(`Capture did not stabilize: ${label}`);
  }
  try {
    start(); await ready();
    const state = makeState(home, env).state;
    const project = await state.project("Remote workspace");
    await state.thread({project, title:"Reconnect demonstration", messages:["Summarize this workspace"], model:"multi-turn"});
    await app.open("/settings/connections");
    await screen.getByRole("button", "Add environment").tap();
    const pairingUrl = new URL(/pairingUrl:\s*(\S+)/.exec(output)![1]);
    pairingUrl.host = `127.0.0.1:${port}`;
    const pairing = pairingUrl.toString();
    await browser.evaluate(() => { for (const input of document.querySelectorAll("input")) input.style.setProperty("-webkit-text-security", "disc"); });
    await screen.getByRole("textbox", "Host").fill(pairing);
    await screen.getByRole("dialog").getByRole("button", "Add environment").tap();
    await screen.getByRole("dialog", "Add Environment").waitFor({state:"hidden"});
    await app.open("/");
    await screen.getByRole("button", "Reconnect demonstration, Done, Remote workspace").tap();
    // Wait for the remote thread to render before stopping its server.
    await screen.getByRole("textbox").waitFor({state:"visible"});
    await screen.getByText("first fixture turn complete").waitFor({state:"visible"});
    await stop();
    // Real repeated network failures let the client's backoff grow while foregrounded.
    await delay(90_000);
    await capture("reconnecting-retry");
    let missingRetry: unknown;
    try { await expect(screen.getByRole("button", "Retry now")).toBeVisible(); }
    catch (error) { missingRetry = error; }
    start(); await ready();
    await capture("server-restored");
    if (missingRetry) throw missingRetry;
    await screen.getByRole("button", "Retry now").tap();
    await screen.getByText("Scenario is reconnecting", {exact:false}).waitFor({state:"hidden"});
    await capture("reconnected");
    await delay(1500);
  } finally { await stop(); }
});
