import { spawn } from "node:child_process";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { createServer } from "node:net";
import { expect } from "e2e";
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 environment offers immediate retry in chat", WEB, async ({ t3, app, screen, browser }) => {
  const stableScreenshot = async (label: string) => {
    let previous = await readFile(await app.screenshot(`${label}-stability`));
    for (let attempt = 0; attempt < 10; attempt++) {
      await delay(500);
      const current = await readFile(await app.screenshot(label));
      if (previous.equals(current)) return;
      previous = current;
    }
    throw new Error("The screenshot did not stabilize");
  };
  const home = `${T3_HOME}-secondary-${Date.now()}`;
  await mkdir(`${home}/userdata`, { recursive: true });
  await writeFile(`${home}/userdata/settings.json`, await readFile(`${T3_HOME}/userdata/settings.json`));
  const socket = createServer();
  await new Promise<void>(resolve => socket.listen(0, "127.0.0.1", resolve));
  const port = (socket.address() as {port: number}).port;
  await new Promise<void>(resolve => socket.close(() => resolve()));
  let pairUrl = "";
  let allowedOrigin = "";
  let child: ReturnType<typeof spawn>;
  const start = () => {
    child = spawn(process.execPath, ["scripts/dev-runner.ts", "dev:server", "--home-dir", home, "--no-seed", "--port", String(port)], {
      cwd: APP_DIR, detached: true, env: { ...process.env, PATH: `${T3_HOME}/bin:${process.env.PATH}`, T3CODE_DEV_ALLOWED_ORIGINS: allowedOrigin, T3CODE_DEV_SEED: "0", T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "0" }, stdio: ["ignore", "pipe", "pipe"]
    });
    child.stdout!.on("data", chunk => { const match = /pairingUrl:\s*(\S+)/.exec(String(chunk)); if (match) pairUrl = match[1]!; });
    child.stderr!.on("data", () => {});
  };
  const stop = async () => { if (child.pid) { process.kill(-child.pid, "SIGTERM"); await new Promise(resolve => child.once("exit", resolve)); } };
  try {
    await t3.project("Local project");
    await t3.open();
    await screen.getByRole("button", "Add project", {exact: true}).waitFor();
    allowedOrigin = new URL(await browser.url()).origin;
    start();
    for (let i = 0; !pairUrl && i < 180; i++) await delay(1000);
    if (!pairUrl) throw new Error("Secondary environment did not start");
    const secondary = makeState(home);
    const project = await secondary.state.project("Remote project");
    await secondary.state.thread({ project, title: "Reconnect demonstration", messages: ["Create a small task list"], model: "todo-list" });
    await app.open("/settings/connections");
    await screen.getByRole("button", "Add environment", {exact: true}).tap();
    const pairing = new URL(pairUrl);
    pairing.host = `127.0.0.1:${port}`;
    await screen.getByRole("textbox", "Host", {exact: true}).fill(pairing.toString());
    await screen.getByRole("dialog").getByRole("button", "Add environment", {exact: true}).tap();
    await screen.getByRole("dialog", "Add Environment", {exact: true}).waitFor({state: "hidden"});
    await screen.getByRole("dialog", "Settings", {exact: true}).getByRole("button", "Close", {exact: true}).tap();
    await screen.getByRole("button", "Reconnect demonstration, Done, Remote project", {exact: true}).tap();
    await delay(2000);
    await stop();
    await delay(75000);
    start();
    for (let i = 0; i < 120; i++) { try { if ((await fetch(`http://127.0.0.1:${port}/.well-known/t3/environment`)).ok) break; } catch {} await delay(100); }
    await stableScreenshot("reconnecting-banner");
    await expect(screen.getByRole("button", "Retry now", {exact: true})).toBeVisible();
    await screen.getByRole("button", "Retry now", {exact: true}).tap();
    await expect(screen.getByRole("button", "Retry now", {exact: true})).not.toBeVisible();
    await stableScreenshot("connected-after-retry");
  } finally { if (child! && child.exitCode === null) await stop(); }
});
