// Repro + regression guard for the user report: "On a totally fresh install
// (no existing data dir) on macOS, Executor does not detect any tools for a
// STDIO MCP server."
//
// `withLocalServer` boots a real `executor web` on a THROWAWAY data dir (the
// fresh-install condition) and the `local` app is the only surface that enables
// stdio MCP (`dangerouslyAllowStdioMCP: true`). We add a stdio MCP server over
// the bearer-authed API and assert its tools are discoverable — and that the
// secret env it needs is stored on the connection (the secret store), not in
// the integration's config blob.
//
// The original bug: `mcp.addServer` only registered an INTEGRATION. Per the
// v1.5 integrations/connections split, tools are produced per-CONNECTION, and a
// stdio add never created one, so the integration landed with zero connections
// and zero tools. The fix auto-creates the default connection on add and routes
// the env values into the connection's secret store.
import { fileURLToPath } from "node:url";

import { expect } from "@effect/vitest";
import { Effect } from "effect";
import { HttpApiClient } from "effect/unstable/httpapi";
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
import { composePluginApi } from "@executor-js/api/server";
import { mcpHttpPlugin } from "@executor-js/plugin-mcp/api";
import { AuthTemplateSlug, ConnectionName, IntegrationSlug } from "@executor-js/sdk/shared";

import { scenario } from "../src/scenario";
import { Cli, RunDir } from "../src/services";
import { withLocalServer } from "./local-server";

const api = composePluginApi([mcpHttpPlugin()] as const);

const FIXTURE = fileURLToPath(new URL("./fixtures/stdio-mcp-server.mjs", import.meta.url));

// The fixture exposes `whoami` ONLY when EXECUTOR_E2E_SECRET is present in its
// process env. So `whoami` showing up in the discovered tools is direct proof
// the connection's secret env reached the spawned subprocess.
const SECRET = "s3cr3t-from-the-vault";

scenario(
  "Local · a stdio MCP server's tools are detected on a fresh install, with env stored as a secret",
  // Must stay STRICTLY greater than the boot-URL wait in `withLocalServer`
  // (currently 240s). When this CI job runs `stdio-mcp.test.ts` alone it always
  // pays a cold `vite optimizeDeps` boot (no prior file to warm the cache), the
  // one variable step. If this test timeout equals the boot wait, both deadlines
  // fire together and vitest's generic "Test timed out" wins, swallowing the
  // harness's "printed no ?_token URL\n<terminal tail>" error that tells us what
  // boot actually got stuck on. Keep the gap so the boot diagnostic surfaces.
  { timeout: 300_000 },
  Effect.gen(function* () {
    const cli = yield* Cli;
    const runDir = yield* RunDir;

    yield* withLocalServer(cli, runDir, (server) =>
      Effect.gen(function* () {
        const client = yield* HttpApiClient.make(api, {
          baseUrl: new URL("/api", server.origin).toString(),
          transformClient: HttpClient.mapRequest((request) =>
            HttpClientRequest.setHeader(request, "authorization", `Bearer ${server.token}`),
          ),
        }).pipe(Effect.provide(FetchHttpClient.layer));

        const slug = "e2e-stdio";

        // Add the stdio server exactly as the desktop/local "Add MCP" flow does,
        // including a secret env var the server needs.
        yield* client.mcp.addServer({
          payload: {
            transport: "stdio",
            name: "E2E Stdio",
            command: "node",
            args: [FIXTURE],
            env: { EXECUTOR_E2E_SECRET: SECRET },
            slug,
          },
        });

        // The integration lands in the catalog — the add itself works.
        const integrations = yield* client.integrations.list();
        expect(
          integrations.map((i) => String(i.slug)),
          "the stdio MCP integration is registered",
        ).toContain(slug);

        // The add auto-creates the default connection (the v1.5 split makes this
        // the thing that drives tool discovery). Pre-fix there were zero.
        const connections = yield* client.connections.list({ query: { integration: slug } });
        expect(
          connections.map((c) => String(c.name)),
          "a default connection was auto-created for the stdio server",
        ).toContain("default");

        // THE SYMPTOM, fixed: the stdio server's tools are detected. `whoami`
        // appearing proves the connection's secret env reached the subprocess.
        const tools = yield* client.tools.list({ query: { integration: slug } });
        const names = tools.map((t) => t.name);
        expect(names, "the stdio server's base tool is detected").toContain("echo_tool");
        expect(
          names,
          "the secret env var reached the spawned subprocess (whoami is gated on it)",
        ).toContain("whoami");

        // "Properly store auth": the secret value is NOT in the integration's
        // config blob — only the var NAME is declared there; the value lives on
        // the connection (the secret store).
        const stored = yield* client.mcp.getServer({ params: { slug } });
        expect(
          JSON.stringify(stored?.config ?? {}),
          "the secret value is not persisted in the integration config",
        ).not.toContain(SECRET);

        // --- The UI path: DECLARE env var names, then provide the secret value
        // as a connection credential (what the add form now does). ---
        const declSlug = "e2e-stdio-decl";
        yield* client.mcp.addServer({
          payload: {
            transport: "stdio",
            name: "E2E Stdio Declared",
            command: "node",
            args: [FIXTURE],
            envVars: ["EXECUTOR_E2E_SECRET"],
            slug: declSlug,
          },
        });

        // Declaring a secret env var (no value) does NOT auto-connect: the
        // secret is still missing, so there are no tools until you connect.
        const beforeConns = yield* client.connections.list({ query: { integration: declSlug } });
        expect(beforeConns, "no connection until the secret is provided").toHaveLength(0);
        const beforeTools = yield* client.tools.list({ query: { integration: declSlug } });
        expect(beforeTools, "no tools until the secret is provided").toHaveLength(0);

        // Provide the secret as the connection credential (the connect step).
        yield* client.connections.create({
          payload: {
            owner: "org",
            name: ConnectionName.make("default"),
            integration: IntegrationSlug.make(declSlug),
            template: AuthTemplateSlug.make("env"),
            values: { EXECUTOR_E2E_SECRET: SECRET },
          },
        });

        const declTools = yield* client.tools.list({ query: { integration: declSlug } });
        expect(
          declTools.map((t) => t.name),
          "connecting with the secret discovers the env-gated tool",
        ).toContain("whoami");
      }),
    );
  }),
);
