import { expect } from "e2e";
import { spawn } from "node:child_process";
import { readFile, writeFile } from "node:fs/promises";
import { createServer } from "node:net";
import * as NetService from "@t3tools/shared/Net";
import * as NodeCrypto from "@effect/platform-node/NodeCrypto";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as References from "effect/References";
import { test, WEB, makeState, runPromise } from "./t3.ts";
import { APP_DIR, T3_HOME } from "../e2e.config.ts";
import { prepareHome } from "./start-app.ts";
import * as EnvironmentAuth from "../../apps/server/src/auth/EnvironmentAuth.ts";
import * as ServerConfig from "../../apps/server/src/config.ts";
import { resolveCliAuthConfig } from "../../apps/server/src/cli/config.ts";
import { mcpToolSession } from "../../apps/server/src/mcp/AcpMcpStdioBridge.ts";

const port = () => new Promise<number>((resolve, reject) => {
  const server = createServer();
  server.on("error", reject);
  server.listen(0, "127.0.0.1", () => {
    const address = server.address();
    const value = typeof address === "object" && address ? address.port : 0;
    server.close(() => resolve(value));
  });
});

test("Project opening and delete fallback skip a remote task shown under its parent", WEB, async ({ t3, app, screen, browser }) => {
  await browser.addInitScript(() => {
    document.addEventListener("DOMContentLoaded", () => {
      const style = document.createElement("style");
      style.textContent = '[data-command-palette] [role="option"] span.min-w-0.flex-1.truncate { color: transparent !important; }';
      document.head.append(style);
    });
  });
  const bHome = `${T3_HOME}-remote`;
  await runPromise(prepareHome(bHome));
  const settingsPath = `${bHome}/userdata/settings.json`;
  const settings = JSON.parse(await readFile(settingsPath, "utf8"));
  await writeFile(settingsPath, JSON.stringify({ ...settings, linkedEnvironmentsEnabled: true }));
  const bPort = await port();
  const bOrigin = `http://127.0.0.1:${bPort}`;
  const logs: string[] = [];
  let pairingResolve!: (url: string) => void;
  const paired = new Promise<string>((resolve) => { pairingResolve = resolve; });
  const child = spawn(process.execPath, ["scripts/dev-runner.ts", "dev:server", "--home-dir", bHome, "--no-seed", "--port", String(bPort), "--dev-url", app.baseUrl!], {
    cwd: APP_DIR,
    env: { ...process.env, PATH: `${bHome}/bin:${process.env.PATH}`, T3CODE_DEV_SEED: "0", T3CODE_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "0", T3CODE_DEV_ALLOWED_ORIGINS: app.baseUrl! },
    stdio: ["ignore", "pipe", "pipe"],
  });
  child.stdout.on("data", (chunk) => {
    const text = chunk.toString();
    logs.push(text.replace(/pairingUrl:\s*\S+/g, "pairingUrl: [redacted]"));
    const url = /pairingUrl:\s*(\S+)/.exec(text)?.[1];
    if (url) pairingResolve(url);
  });
  child.stderr.on("data", (chunk) => logs.push(chunk.toString()));
  const remote = makeState(bHome);
  try {
    const pairingUrl = await Promise.race([paired, new Promise<never>((_, reject) => child.once("exit", (code) => reject(new Error(`Remote server exited ${code}: ${logs.join("")}`))))]);
    const bProject = await remote.state.project("demo");
    const older = await remote.state.thread({ project: bProject, title: "B older ordinary", messages: ["older"] });
    const deleting = await remote.state.thread({ project: bProject, title: "B open then delete", messages: ["delete me"] });
    const aProject = await t3.project("demo A");
    const parent = await t3.thread({ project: aProject, title: "A parent delegating", messages: ["parent"] });
    const aEnvironment = (await (await fetch(`${app.baseUrl}/.well-known/t3/environment`)).json()).environmentId;
    const bEnvironment = (await (await fetch(`${bOrigin}/.well-known/t3/environment`)).json()).environmentId;
    // A peer's launch uses the real linked-environment MCP boundary and persists delegatedFrom.
    await runPromise(Effect.gen(function* () {
      const config = yield* resolveCliAuthConfig({ baseDir: Option.some(bHome) }, Option.none(), { quietLogs: true });
      yield* Effect.gen(function* () {
        const auth = yield* EnvironmentAuth.EnvironmentAuth;
        const issued = yield* Effect.acquireRelease(auth.issueMcpClientSession({ label: "T3 Code", access: "full-access", peerLink: true, client: { deviceType: "bot" } }), (session) => auth.revokeSession(session.sessionId));
        const session = yield* mcpToolSession({ endpoint: `${bOrigin}/mcp`, authorization: `Bearer ${issued.token}`, clientName: "scenario-peer" });
        const projects = yield* session.call("t3_project_list", {});
        const project = (projects as any).structuredContent.projects.find((p: any) => p.title === "demo");
        const task = yield* session.call("t3_thread_launch", { projectId: project.id, title: "go", modelSelection: { instanceId: "devReplay", model: "multi-turn" }, message: "go", delegatedFrom: { environmentId: aEnvironment, threadId: parent.threadId, title: "A parent delegating" } });
        const result = (task as any).structuredContent;
        if (!result?.threadId) throw new Error(JSON.stringify(task));
        yield* session.call("t3_thread_wait", { threadId: result.threadId, runId: result.runId, timeoutMs: 120000 });
      }).pipe(Effect.scoped, Effect.provide(EnvironmentAuth.layerRuntime.pipe(Layer.provide(ServerConfig.layer(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
    }).pipe(Effect.provide([NetService.layer, NodeCrypto.layer])));
    await t3.open(parent);
    const tokenUrl = new URL(pairingUrl);
    const token = new URLSearchParams(tokenUrl.hash.slice(1)).get("token") ?? tokenUrl.searchParams.get("token");
    await screen.getByRole("heading", "A parent delegating", { exact: true }).waitFor();
    await browser.evaluate(async ({ host, token }) => {
      const { connectPairing } = await import("/src/connection/onboarding.ts");
      const { appAtomRegistry } = await import("/src/rpc/atomRegistry.ts");
      const result = await connectPairing.run(appAtomRegistry, { host, pairingCode: token });
      if (result._tag !== "Success") throw new Error("Remote browser pairing failed");
    }, { host: bOrigin, token });
    await app.open(`/${aEnvironment}/${parent.threadId}`);
    await screen.getByRole("button", "B older ordinary", { exact: false }).waitFor({ state: "visible" });
    await app.screenshot("setup-parent-and-listed-threads");
    await browser.keyboard.press("Control+k");
    await screen.getByRole("dialog", "Command palette").getByRole("combobox").fill("demo");
    await screen.getByRole("option", /^demo /).filter({ hasText: bProject }).tap();
    await screen.getByRole("dialog", "Command palette").getByRole("combobox").waitFor({ state: "hidden" });
    await app.screenshot("project-open");
    const paletteUrl = await browser.evaluate(() => decodeURIComponent(location.pathname));
    await app.open(`/${bEnvironment}/${deleting.threadId}`);
    await screen.getByRole("button", "B open then delete", { exact: false }).secondaryTap();
    await screen.getByRole("menuitem", "Delete", { exact: true }).tap();
    await screen.getByRole("button", "Confirm", { exact: true }).tap();
    await screen.getByRole("button", "Confirm", { exact: true }).waitFor({ state: "hidden" });
    await app.screenshot("delete-fallback");
    const deleteUrl = await browser.evaluate(() => decodeURIComponent(location.pathname));
    expect({ palette: paletteUrl, deleteFallback: deleteUrl }).toEqual({ palette: `/${bEnvironment}/${deleting.threadId}`, deleteFallback: `/${bEnvironment}/${older.threadId}` });
  } finally {
    child.kill("SIGTERM");
    await new Promise<void>((resolve) => { if (child.exitCode !== null) resolve(); else child.once("exit", () => resolve()); });

  }
});
