import { expect } from "e2e";
import { spawn, execFileSync } from "node:child_process";
import { mkdirSync, writeFileSync, openSync, readFileSync, mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { APP_DIR } from "../e2e.config.ts";
import { test, WEB } from "./t3.ts";

test("Re-pairing refreshes every route, including after removing the new route", WEB, async ({ t3, app, screen, browser }) => {
  const scratch = mkdtempSync(join(tmpdir(), "route-login-proof-"));
  const home = `${scratch}/host`;
  const capture = async (label: string) => {
    for (let attempt = 0; attempt < 20; attempt++) {
      const first = await app.screenshot(`${label}-stability-a`);
      await delay(500);
      const second = await app.screenshot(`${label}-stability-b`);
      if (readFileSync(first).equals(readFileSync(second))) return app.screenshot(label);
    }
    throw new Error("The capture did not settle");
  };
  mkdirSync(`${scratch}/bin`, { recursive: true });
  writeFileSync(`${scratch}/bin/hostnamectl`, '#!/bin/sh\necho "Route demo"\n', { mode: 0o755 });
  const env = { ...process.env, T3CODE_DEV_SEED: "0", PATH: `${scratch}/bin:${process.env.PATH}` };
  const host = spawn(process.execPath, ["apps/server/src/bin.ts", "serve", "--base-dir", home, "--port", "39771", "--host", "0.0.0.0"], { cwd: APP_DIR, env, stdio: ["ignore", openSync(`${scratch}/host.log`, "w"), openSync(`${scratch}/host-error.log`, "w")] });
  const cli = (...args: string[]) => execFileSync(process.execPath, ["apps/server/src/bin.ts", ...args, "--base-dir", home], { cwd: APP_DIR, env, encoding: "utf8" });
  try {
    for (let attempt = 0; attempt < 120; attempt++) {
      try { if ((await fetch("http://127.0.0.1:39771/.well-known/t3/environment")).ok) break; } catch {}
      if (host.exitCode !== null) throw new Error("Isolated host exited before ready");
      await delay(500);
    }
    await t3.project("Route proof");
    await t3.open();
    await screen.getByText("Route proof", { exact: true }).first().waitFor({ timeout: 120000 });
    await app.open("/settings/connections");
    const pair = async (address: string, action: string) => {
      const output = cli("auth", "pairing", "create", "--json");
      const token = JSON.parse(output).credential;
      await screen.getByRole("button", action).tap();
      const dialog = screen.getByRole("dialog", action === "Add environment" ? "Add Environment" : /^Add a route to /);
      await dialog.getByLabel("Host").fill(address);
      // Mask the pairing input; recordings are additionally redacted before publication.
      await browser.evaluate(() => { const input = document.querySelector('input[placeholder="PAIRCODE"]'); input?.setAttribute("type", "password"); });
      await dialog.getByLabel("Pairing code").fill(token);
      await dialog.getByRole("button", action).tap();
      await dialog.waitFor({ state: "hidden" });
      await screen.getByText(/ · Connected$/).first().waitFor({ timeout: 120000 });
    };
    await pair("http://127.0.0.1:39771", "Add environment");
    await screen.getByRole("button", /^(\d+ routes|Routes)$/).last().tap();
    const sessions = JSON.parse(cli("auth", "session", "list", "--json"));
    for (const session of sessions) cli("auth", "session", "revoke", session.sessionId);
    await app.restart();
    await app.open("/settings/connections");
    await screen.getByText("did not accept this device", { exact: false }).waitFor();
    await screen.getByRole("button", /^(\d+ routes|Routes)$/).last().tap();
    await pair("http://127.0.0.2:39771", "Add route");
    await capture("repaired-before-removal");
    const newRoute = screen.getByRole("listitem").filter({ hasText: "http://127.0.0.2:39771" });
    await newRoute.getByRole("button", /^Remove .* route$/).tap();
    await screen.getByRole("button", "Confirm").tap();
    // Reconnect from persisted routes, as a real reload does, before judging the result.
    await app.restart();
    await app.open("/settings/connections");
    await screen.getByRole("button", /^(\d+ routes|Routes)$/).last().tap();
    try {
      await expect(screen.getByText(/ · Connected$/)).toBeVisible({ timeout: 20000 });
    } finally {
      await capture("after-removing-new-route");
    }
  } finally {
    host.kill("SIGTERM");
    await new Promise<void>((resolve) => { if (host.exitCode !== null) resolve(); else host.once("exit", () => resolve()); });
  }
});
