import { expect } from "e2e";
import { spawn, execFileSync } from "node:child_process";
import { readFileSync, writeFileSync, mkdirSync, existsSync, watch } from "node:fs";
import { createServer } from "node:net";
import { join } from "node:path";
import { test, WEB, makeState } from "./t3.ts";
import { APP_DIR, T3_HOME } from "../e2e.config.ts";

// Two real servers, synthetic state, and the stock replay agent behind a credential-capture proxy.
// The proxy holds the coordinator's prompt open so its MCP calls have a live thread scope.
const scratch = process.env.T3_PROOF_SCRATCH;
if (!scratch) throw new Error("Set T3_PROOF_SCRATCH to a private scratch directory.");
const waitFile = async (file: string) => {
  if (existsSync(file)) return;
  await new Promise<void>((resolve) => {
    let ancestor = join(file, "..");
    while (!existsSync(ancestor)) ancestor = join(ancestor, "..");
    const watcher = watch(ancestor, { recursive: true }, () => {
      if (existsSync(file)) { watcher.close(); resolve(); }
    });
  });
};
const port = () => new Promise<number>((resolve) => {
  const server = createServer();
  server.listen(0, "127.0.0.1", () => {
    const value = (server.address() as { port: number }).port;
    server.close(() => resolve(value));
  });
});
const cli = (home: string, ...args: string[]) => execFileSync(process.execPath,
  ["apps/server/src/bin.ts", ...args, "--base-dir", home],
  { cwd: APP_DIR, encoding: "utf8", env: { ...process.env, T3CODE_DEV_SEED: "0" } }).trim();
const settings = (home: string, patch: object) => {
  const file = join(home, "userdata/settings.json");
  writeFileSync(file, JSON.stringify({ ...JSON.parse(readFileSync(file, "utf8")), ...patch }));
};
const mcp = async (endpoint: string, authorization: string) => {
  let session: string | null = null;
  let id = 0;
  const request = async (method: string, params: object, notification = false) => {
    const res = await fetch(endpoint, { method: "POST", headers: {
      "content-type": "application/json", accept: "application/json, text/event-stream", authorization,
      ...(session ? { "mcp-session-id": session, "mcp-protocol-version": "2025-06-18" } : {}),
    }, body: JSON.stringify({ jsonrpc: "2.0", ...(!notification ? { id: ++id } : {}), method, params }) });
    session = res.headers.get("mcp-session-id") ?? session;
    const text = await res.text();
    if (!res.ok) throw new Error(`MCP HTTP ${res.status}: ${text}`);
    if (notification) return;
    const frames = text.startsWith("{") ? [JSON.parse(text)] : text.split("\n").filter(x => x.startsWith("data: ")).map(x => JSON.parse(x.slice(6)));
    const result = frames.find(x => x.id === id);
    if (result?.error) throw new Error(JSON.stringify(result.error));
    return result?.result;
  };
  await request("initialize", { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "scenario", version: "1" } });
  await request("notifications/initialized", {}, true);
  return async (name: string, args: object) => {
    const result = await request("tools/call", { name, arguments: args });
    if (result.isError) throw new Error(JSON.stringify(result));
    return result.structuredContent ?? JSON.parse(result.content[0].text);
  };
};

test("A linked launch with a claimed parent stays in the standalone sidebar", WEB, async ({ t3, app, screen, browser }) => {
  const run = join(scratch, `run-${Date.now()}`);
  mkdirSync(run, { recursive: true });
  const hostLabel = join(run, "host-label.mjs");
  writeFileSync(hostLabel, `import os from "node:os";import{syncBuiltinESMExports}from"node:module";os.hostname=()=>"Scenario";syncBuiltinESMExports();`);
  process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ""} --import=${hostLabel}`;
  const stableScreenshot = async (label: string) => {
    for (let attempt = 0; attempt < 40; attempt++) {
      const first = await app.screenshot(`${label}-stability-probe`);
      // The manual evidence protocol requires identical captures 500 ms apart.
      await new Promise(resolve => setTimeout(resolve, 500));
      const second = await app.screenshot(label);
      if (readFileSync(first).equals(readFileSync(second))) {
        writeFileSync(join(run, `${label}-stable.json`), JSON.stringify({ first, second, gapMs: 500 }));
        return;
      }
    }
    throw new Error(`The ${label} capture did not stabilize.`);
  };
  await browser.emulateMedia({ colorScheme: "light", reducedMotion: "reduce" });
  const bHome = join(run, "environment-b");
  const bPort = await port();
  const log = await import("node:fs").then(fs => fs.openSync(join(run, "environment-b.log"), "w"));
  mkdirSync(join(bHome, "userdata"), { recursive: true });
  writeFileSync(join(bHome, "userdata/settings.json"), readFileSync(join(T3_HOME, "userdata/settings.json")));
  const b = spawn(process.execPath, ["scripts/dev-runner.ts", "dev:server", "--home-dir", bHome, "--no-seed", "--port", String(bPort), "--dev-url", `http://127.0.0.1:${bPort}`],
    { cwd: APP_DIR, env: { ...process.env, T3CODE_DEV_SEED: "0", T3CODE_DEV_ALLOWED_ORIGINS: app.baseUrl, T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "0", PATH: `${join(T3_HOME, "bin")}:${process.env.PATH}` }, stdio: ["ignore", "pipe", log], detached: true });
  let output = "";
  b.stdout!.on("data", chunk => {
    output += chunk.toString();
    writeFileSync(join(run, "environment-b-stdout.log"), output);
    const pairing = /pairingUrl:\s*(\S+)/.exec(output)?.[1];
    if (pairing) { const url = new URL(pairing); writeFileSync(join(bHome, "pairing-path"), url.pathname + url.search + url.hash); }
  });
  try {
    await waitFile(join(bHome, "pairing-path"));
    settings(bHome, { linkedEnvironmentsEnabled: true });
    const bState = makeState(bHome).state;
    const remoteWorkspace = await bState.project("Remote workspace");
    await bState.thread({ project: remoteWorkspace, title: "Existing remote work", messages: ["Prepare the remote workspace."] });
    const projectB = execFileSync("sqlite3", ["-readonly", join(bHome, "userdata/statev2.sqlite"), "SELECT project_id FROM projection_projects LIMIT 1"], { encoding: "utf8" }).trim();
    const descriptorB = await fetch(`http://127.0.0.1:${bPort}/.well-known/t3/environment`).then(r => r.json());
    const pair = JSON.parse(cli(bHome, "auth", "pairing", "create", "--json"));
    const aSettings = JSON.parse(readFileSync(join(T3_HOME, "userdata/settings.json"), "utf8"));
    const credentials = join(run, "agent-credential.json");
    const wrapper = join(run, "replay-proxy.mjs");
    writeFileSync(wrapper, `import {spawn} from 'node:child_process';import{writeFileSync}from'node:fs';import{createInterface}from'node:readline';
const child=spawn(process.execPath,[${JSON.stringify(join(APP_DIR, "apps/server/scripts/dev-replay-agent.ts"))}],{stdio:['pipe','pipe','inherit']});
createInterface({input:process.stdin}).on('line',line=>{const m=JSON.parse(line);if(m.method==='session/new'||m.method==='session/load'){writeFileSync(${JSON.stringify(join(run, "session-private.json"))},line);const e=Object.fromEntries((m.params.mcpServers??[]).flatMap(s=>s.env??[]).map(x=>[x.name,x.value]));if(e.T3_ACP_MCP_ENDPOINT)writeFileSync(${JSON.stringify(credentials)},JSON.stringify({endpoint:e.T3_ACP_MCP_ENDPOINT,authorization:e.T3_ACP_MCP_AUTHORIZATION}));}child.stdin.write(line+'\\n')});
const input=createInterface({input:child.stdout});input.on('line',line=>{const m=JSON.parse(line);if(m.result?.stopReason)return;process.stdout.write(line+'\\n')});process.on('SIGTERM',()=>{child.kill();process.exit()});`);
    settings(T3_HOME, { linkedEnvironmentsEnabled: true, providerInstances: { ...aSettings.providerInstances,
      devReplay: { ...aSettings.providerInstances.devReplay, config: { ...aSettings.providerInstances.devReplay.config, commandArgs: [wrapper] } } } });
    cli(T3_HOME, "environment", "link", `http://127.0.0.1:${bPort}`, "--pairing-code", pair.credential, "--access", "full-access");
    const workspace = await t3.project("Coordinator workspace");
    const parent = JSON.parse(cli(T3_HOME, "thread", "start", workspace, "Coordinate remote work.", "--title", "Coordinator", "--provider", "devReplay", "--model", "multi-turn", "--json"));
    await waitFile(credentials);
    const agent = JSON.parse(readFileSync(credentials, "utf8"));
    const call = await mcp(agent.endpoint, agent.authorization);
    await t3.open();
    await screen.getByTestId("sidebar-working-shelf-toggle").waitFor({ timeout: 120_000 });
    await screen.getByTestId("sidebar-working-shelf-toggle").tap();
    await app.open("/settings/connections");
    await screen.getByRole("button", "Add environment", { exact: true }).first().tap();
    await screen.getByPlaceholder("backend.example.com").fill(`http://127.0.0.1:${bPort}${readFileSync(join(bHome, "pairing-path"), "utf8")}`);
    await screen.getByRole("dialog").getByRole("button", "Add environment", { exact: true }).tap();
    await screen.getByRole("dialog", "Add Environment", { exact: true }).waitFor({ state: "hidden" });
    const launched = await call("t3_thread_launch", {
      environmentId: descriptorB.environmentId, projectId: projectB, title: "Standalone remote launch",
      modelSelection: { instanceId: "devReplay", model: "multi-turn" }, message: "Prepare a standalone report.",
      clientRequestId: "standalone-proof", delegatedFrom: { environmentId: agent.environmentId ?? (await fetch(new URL("/.well-known/t3/environment", agent.endpoint)).then(r => r.json())).environmentId,
        threadId: parent.threadId, title: "Coordinator" },
    });
    writeFileSync(join(run, "launched.json"), JSON.stringify(launched));
    await call("orchestrator_capabilities", {});
    const delegated = await call("delegate_task", { task: "Prepare a delegated report.", title: "Real delegate",
      target: { environmentId: descriptorB.environmentId, projectId: projectB, providerInstanceId: "devReplay", model: "multi-turn" },
      clientRequestId: "delegated-proof" });
    writeFileSync(join(run, "delegated.json"), JSON.stringify(delegated));
    const aId = (await fetch(new URL("/.well-known/t3/environment", agent.endpoint)).then(r => r.json())).environmentId;
    // The returned launch link opens even on base; show its real conversation first.
    await app.open(`/${descriptorB.environmentId}/${launched.threadId}`);
    await screen.getByRole("heading", "Standalone remote launch", { exact: true }).waitFor();
    await screen.getByText("first fixture turn complete", { exact: true }).waitFor();
    await screen.getByText("Syncing messages…", { exact: false }).waitFor({ state: "hidden" });
    await stableScreenshot("launched-conversation");
    await app.open(`/${aId}/${parent.threadId}`);
    await screen.getByRole("heading", "Coordinator", { exact: true }).waitFor();
    await screen.getByTestId("sidebar-settled-shelf-toggle").tap();
    await screen.getByRole("button", /^Previous agents/).tap();
    await stableScreenshot("standalone-sidebar");
    await expect(screen.getByRole("navigation", "Threads").getByRole("button", /^Standalone remote launch,/)).toBeVisible();
    await expect(screen.getByRole("region", "Lineage").getByRole("button", /^Real delegate on Scenario/)).toBeVisible();
  } finally {
    if (b.pid) process.kill(-b.pid, "SIGTERM");
  }
});
