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

test("A silent remote socket recovers without showing reconnect", WEB, async ({ t3, app, screen, browser }) => {
  const project = await t3.project("Connection demo");
  await t3.thread({ project, title: "Earlier work", messages: ["Hello"] });
  await browser.addInitScript(() => {
    const connection = new EventTarget();
    Object.assign(connection, { type: "wifi" });
    Object.defineProperty(navigator, "connection", { value: connection, configurable: true });
  });
  await t3.open();
  const origin = await browser.evaluate(() => location.origin);
  const target = new URL(origin);
  const host = execFileSync("tailscale", ["ip", "-4"], { encoding: "utf8" }).trim().split("\n")[0];
  const sockets = new Set<net.Socket>();
  const established: Array<{ blocked: boolean }> = [];
  let recovering = false;
  let replacements = 0;
  const proxy = net.createServer((client) => {
    sockets.add(client);
    client.once("data", (first) => {
      const websocket = /upgrade: websocket/i.test(first.toString());
      const state = { blocked: false };
      if (websocket) established.push(state);
      const upstream = net.connect(Number(target.port), target.hostname);
      sockets.add(upstream);
      const start = () => {
        upstream.write(first.toString().replace(/Origin: [^\r]+/i, `Origin: ${origin}`).replace(/Host: [^\r]+/i, `Host: ${target.host}`));
        client.on("data", (data) => { if (!state.blocked) upstream.write(data); });
        upstream.on("data", (data) => { if (!state.blocked) client.write(data); });
      };
      upstream.on("connect", () => {
        if (websocket && recovering) { replacements++; setTimeout(start, 1500); }
        else start();
      });
      client.on("close", () => upstream.destroy());
      upstream.on("close", () => client.destroy());
      upstream.on("error", () => client.destroy());
    });
    client.on("error", () => {});
  });
  await new Promise<void>((resolve) => proxy.listen(0, "0.0.0.0", resolve));
  try {
    const port = (proxy.address() as net.AddressInfo).port;
    const pairing = await readFile(`${T3_HOME}/pairing-path`, "utf8");
    await app.open(`http://${host}:${port}${pairing}`);
    await screen.getByText("Earlier work", { exact: false }).first().waitFor({ timeout: 120_000 });
    await browser.locator("[data-thread-item]").first().tap();
    await screen.getByRole("textbox").fill("Continue after the connection recovers");
    await screen.getByRole("button", "Submit message").hover();
    await app.screenshot("connected-before-interruption");
    // Silence only existing TCP streams. Fresh HTTP and WebSocket connections remain reachable.
    for (const connection of established) connection.blocked = true;
    recovering = true;
    await browser.evaluate(() => {
      const connection = (navigator as Navigator & { connection: EventTarget & { type: string } }).connection;
      connection.type = "cellular";
      connection.dispatchEvent(new Event("change"));
    });
    await new Promise((resolve) => setTimeout(resolve, 3000));
    await app.screenshot("remote-socket-handoff");
    const text = await browser.evaluate(() => document.body.innerText);
    expect(text).not.toMatch(/Reconnecting|Connecting|Disconnected/);
    expect(await browser.evaluate(() => document.querySelector('button[aria-label="Environment disconnected"]') !== null)).toBe(false);
    await new Promise((resolve) => setTimeout(resolve, 1500));
    await app.screenshot("recovered");
  } finally {
    for (const socket of sockets) socket.destroy();
    await new Promise<void>((resolve) => proxy.close(() => resolve()));
  }
});
