// Cloud: MCP sessions driven by the REAL @modelcontextprotocol/sdk Client over
// StreamableHTTP — exactly the code path Claude/Cursor run. The dev server is
// the production wrangler topology (real workerd, real McpSessionDO), so
// session continuity here is real Durable Object state surviving across
// client connections, not a stub.
//
// Ported from apps/cloud/src/mcp-miniflare.e2e.node.test.ts (unstable_dev +
// test-seam bearers) onto the e2e dev server with real OAuth bearers.
// Telemetry-span assertions from that file required injecting an OTLP
// receiver into the worker env and were NOT carried (not black-box).

import { expect } from "@effect/vitest";
import { Effect } from "effect";
import { composePluginApi } from "@executor-js/api/server";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

import { scenario } from "../src/scenario";
import { Api, Mcp, Target } from "../src/services";
import type { Identity } from "../src/target";
import {
  configuredMcpPausedSessionIdleTimeoutMs,
  configuredMcpSessionTimeoutMs,
} from "../setup/mcp-session-timeouts";

const coreApi = composePluginApi([] as const);

const emailOf = (identity: Identity): string => identity.credentials?.email ?? identity.label;

interface Connected {
  readonly client: Client;
  readonly transport: StreamableHTTPClientTransport;
}

/** A real SDK client over StreamableHTTP; `sessionId` resumes an existing session. */
const connectClient = async (
  mcpUrl: string,
  bearer: string,
  sessionId?: string,
): Promise<Connected> => {
  const client = new Client(
    { name: "executor-e2e-sessions", version: "0.0.1" },
    { capabilities: {} },
  );
  const transport = new StreamableHTTPClientTransport(new URL(mcpUrl), {
    requestInit: { headers: { authorization: `Bearer ${bearer}` } },
    ...(sessionId === undefined ? {} : { sessionId }),
  });
  await client.connect(transport);
  return { client, transport };
};

const textOf = (result: { content?: unknown; toolResult?: unknown }): string =>
  ((result.content ?? []) as Array<{ type: string; text?: string }>)
    .filter((part) => part.type === "text")
    .map((part) => part.text)
    .join("\n");

const closeQuietly = (connected: Connected): Effect.Effect<void> =>
  Effect.promise(() => connected.client.close().catch(() => undefined));

scenario(
  "MCP sessions · a real MCP client connects, lists tools, and executes code",
  {},
  Effect.gen(function* () {
    const target = yield* Target;
    const mcp = yield* Mcp;
    const identity = yield* target.newIdentity();
    const bearer = yield* mcp.mintBearer(emailOf(identity));
    const session = yield* Effect.promise(() => connectClient(target.mcpUrl, bearer));
    yield* Effect.gen(function* () {
      expect(
        session.client.getServerVersion()?.name,
        "the handshake reports the product server",
      ).toBe("executor");
      expect(session.transport.sessionId, "the transport holds a session id").toEqual(
        expect.any(String),
      );

      const { tools } = yield* Effect.promise(() => session.client.listTools());
      expect(
        tools.map((tool) => tool.name),
        "the execute tool is advertised",
      ).toContain("execute");
      expect(
        tools.map((tool) => tool.name),
        "the resume tool is advertised",
      ).toContain("resume");

      const result = yield* Effect.promise(() =>
        session.client.callTool({ name: "execute", arguments: { code: "return 6 * 7;" } }),
      );
      expect(result.isError, "the call succeeds").not.toBe(true);
      expect(textOf(result), "the sandbox returns the value").toContain("42");
    }).pipe(Effect.ensuring(closeQuietly(session)));
  }),
);

scenario(
  "MCP sessions · a second client resuming the session id continues the session",
  {},
  Effect.gen(function* () {
    const target = yield* Target;
    const mcp = yield* Mcp;
    const identity = yield* target.newIdentity();
    const bearer = yield* mcp.mintBearer(emailOf(identity));

    // First connection establishes the session, does real work, then goes
    // away — the laptop-closed / process-restarted case.
    const first = yield* Effect.promise(() => connectClient(target.mcpUrl, bearer));
    const sessionId = first.transport.sessionId;
    expect(sessionId, "the first client got a session id").toEqual(expect.any(String));
    const before = yield* Effect.promise(() =>
      first.client.callTool({ name: "execute", arguments: { code: 'return "before";' } }),
    ).pipe(Effect.ensuring(closeQuietly(first)));
    expect(textOf(before), "the first client's call succeeded").toContain("before");

    // A brand-new client resumes with nothing but the session id. The
    // session's Durable Object state persists across connections — this is
    // the restore guarantee.
    const second = yield* Effect.promise(() => connectClient(target.mcpUrl, bearer, sessionId));
    yield* Effect.gen(function* () {
      expect(second.transport.sessionId, "the session id is preserved, not reissued").toBe(
        sessionId,
      );
      const { tools } = yield* Effect.promise(() => second.client.listTools());
      expect(
        tools.map((tool) => tool.name),
        "the resumed session serves requests",
      ).toContain("execute");
      const after = yield* Effect.promise(() =>
        second.client.callTool({ name: "execute", arguments: { code: 'return "after";' } }),
      );
      expect(after.isError, "the resumed session executes code").not.toBe(true);
      expect(textOf(after), "the resumed session returns results").toContain("after");
    }).pipe(Effect.ensuring(closeQuietly(second)));
  }),
);

const IDLE_RUNTIME_DISPOSE_BUFFER_MS = 2_000;
const IDLE_RUNTIME_DISPOSE_GAP_MS =
  configuredMcpSessionTimeoutMs() + IDLE_RUNTIME_DISPOSE_BUFFER_MS;
const IDLE_RUNTIME_DISPOSE_SCENARIO_TIMEOUT_MS = IDLE_RUNTIME_DISPOSE_GAP_MS + 120_000;

scenario(
  "MCP sessions · a resumed session after idle runtime disposal restores cleanly",
  { timeout: IDLE_RUNTIME_DISPOSE_SCENARIO_TIMEOUT_MS },
  Effect.gen(function* () {
    const target = yield* Target;
    const mcp = yield* Mcp;
    const identity = yield* target.newIdentity();
    const bearer = yield* mcp.mintBearer(emailOf(identity));

    const first = yield* Effect.promise(() => connectClient(target.mcpUrl, bearer));
    const sessionId = first.transport.sessionId;
    expect(sessionId, "the client got a session id").toEqual(expect.any(String));
    if (sessionId === undefined) return yield* Effect.die("missing session id");

    const before = yield* Effect.promise(() =>
      first.client.callTool({ name: "execute", arguments: { code: 'return "before-idle";' } }),
    ).pipe(Effect.ensuring(closeQuietly(first)));
    expect(before.isError, "the pre-idle call succeeds").not.toBe(true);
    expect(textOf(before), "the pre-idle call returns results").toContain("before-idle");

    yield* Effect.sleep(IDLE_RUNTIME_DISPOSE_GAP_MS);

    const second = yield* Effect.promise(() => connectClient(target.mcpUrl, bearer, sessionId));
    yield* Effect.gen(function* () {
      expect(second.transport.sessionId, "the session id is preserved").toBe(sessionId);
      const after = yield* Effect.promise(() =>
        second.client.callTool({ name: "execute", arguments: { code: 'return "after-idle";' } }),
      );
      expect(after.isError, "the post-idle call succeeds").not.toBe(true);
      expect(textOf(after), "the post-idle call returns results").toContain("after-idle");
    }).pipe(Effect.ensuring(closeQuietly(second)));
  }),
);

scenario(
  "MCP sessions · an unknown session id fails fast with a clean error, not a hang",
  {},
  Effect.gen(function* () {
    const target = yield* Target;
    const mcp = yield* Mcp;
    const identity = yield* target.newIdentity();
    const bearer = yield* mcp.mintBearer(emailOf(identity));
    // A session id the server never issued (right shape, never minted).
    const session = yield* Effect.promise(() =>
      connectClient(target.mcpUrl, bearer, "0".repeat(64)),
    );
    const failure = yield* Effect.flip(
      Effect.tryPromise({
        try: () => session.client.listTools(),
        catch: (cause) => cause,
      }),
    ).pipe(
      Effect.timeoutOrElse({
        duration: "15 seconds",
        orElse: () =>
          Effect.die(new Error("listTools on an unknown session hung instead of failing")),
      }),
      Effect.ensuring(closeQuietly(session)),
    );
    expect(String(failure), "the server answered with a JSON-RPC error envelope").toContain(
      "jsonrpc",
    );
  }),
);

scenario(
  "MCP sessions · two concurrent clients hold isolated sessions that don't interfere",
  {},
  Effect.gen(function* () {
    const target = yield* Target;
    const mcp = yield* Mcp;
    const identity = yield* target.newIdentity();
    const bearer = yield* mcp.mintBearer(emailOf(identity));
    const [alpha, beta] = yield* Effect.promise(() =>
      Promise.all([connectClient(target.mcpUrl, bearer), connectClient(target.mcpUrl, bearer)]),
    );
    yield* Effect.gen(function* () {
      expect(alpha.transport.sessionId, "each client gets its own session").not.toBe(
        beta.transport.sessionId,
      );

      const [alphaResult, betaResult] = yield* Effect.promise(() =>
        Promise.all([
          alpha.client.callTool({
            name: "execute",
            arguments: {
              code: 'await new Promise((resolve) => setTimeout(resolve, 300));\nreturn "alpha-result";',
            },
          }),
          beta.client.callTool({
            name: "execute",
            arguments: { code: 'return "beta-result";' },
          }),
        ]),
      );
      expect(textOf(alphaResult), "the first session got its own answer").toContain("alpha-result");
      expect(textOf(alphaResult), "no cross-talk into the first session").not.toContain(
        "beta-result",
      );
      expect(textOf(betaResult), "the second session got its own answer").toContain("beta-result");
      expect(textOf(betaResult), "no cross-talk into the second session").not.toContain(
        "alpha-result",
      );
    }).pipe(Effect.ensuring(closeQuietly(alpha)), Effect.ensuring(closeQuietly(beta)));
  }),
);

const APPROVAL_TARGET_TOOL = "executor.coreTools.policies.list";

const GATED_CODE = `
const result = await tools.executor.coreTools.policies.list({});
return JSON.stringify(result);
`;

const executionIdOf = (text: string): string | undefined =>
  /\bexecutionId:\s*(\S+)/.exec(text)?.[1];

const IDLE_TEARDOWN_BUFFER_MS = 2_000;
const IDLE_TEARDOWN_GAP_MS = configuredMcpPausedSessionIdleTimeoutMs() + IDLE_TEARDOWN_BUFFER_MS;
const IDLE_TEARDOWN_SCENARIO_TIMEOUT_MS = IDLE_TEARDOWN_GAP_MS + 120_000;

// The session DO tears its runtime down after the configured idle ceiling and
// rebuilds it from storage on the next request. The e2e harness shortens that
// ceiling when it owns the cloud dev stack. Paused approvals deliberately do
// not survive this (durable pause state is out of scope); the contract is that
// an expired pause fails with recovery guidance and the session keeps working.

scenario(
  "MCP sessions · an approval paused past the idle window expires with re-run guidance, not a dead end",
  { timeout: IDLE_TEARDOWN_SCENARIO_TIMEOUT_MS },
  Effect.gen(function* () {
    const target = yield* Target;
    const { client } = yield* Api;
    const mcp = yield* Mcp;
    const identity = yield* target.newIdentity();
    const api = yield* client(coreApi, identity);
    const bearer = yield* mcp.mintBearer(emailOf(identity));

    const policy = yield* api.policies.create({
      payload: { owner: "org", pattern: APPROVAL_TARGET_TOOL, action: "require_approval" },
    });

    yield* Effect.gen(function* () {
      const first = yield* Effect.promise(() => connectClient(target.mcpUrl, bearer));
      const sessionId = first.transport.sessionId;
      expect(sessionId, "the first client got a session id").toEqual(expect.any(String));
      if (sessionId === undefined) return yield* Effect.die("missing session id");
      const paused = yield* Effect.promise(() =>
        first.client.callTool({ name: "execute", arguments: { code: GATED_CODE } }),
      ).pipe(Effect.ensuring(closeQuietly(first)));
      const pausedText = textOf(paused);
      expect(pausedText, "the gated call pauses instead of completing").toContain(
        "Execution paused",
      );
      const executionId = executionIdOf(pausedText);
      expect(executionId, "the paused result carries the executionId").toEqual(expect.any(String));
      if (executionId === undefined) return yield* Effect.die("missing paused execution id");

      // The user thinks the approval over for longer than the session keeps
      // its runtime warm; the pause is gone when they come back.
      yield* Effect.sleep(IDLE_TEARDOWN_GAP_MS);

      const second = yield* Effect.promise(() => connectClient(target.mcpUrl, bearer, sessionId));
      yield* Effect.gen(function* () {
        const resumed = yield* Effect.promise(() =>
          second.client.callTool({
            name: "resume",
            arguments: { executionId, action: "accept", content: "{}" },
          }),
        );
        const resumedText = textOf(resumed);
        expect(
          resumedText,
          "the error tells the model how to recover, not just that the pause is gone",
        ).toContain("run the execute tool again");
        expect(resumed.isError, "the expired resume is an error, not a silent success").toBe(true);

        // The advertised recovery actually works on the same session: a fresh
        // execute pauses with a NEW id (stale ids are never reused), and
        // resuming that completes the gated call.
        const reExecuted = yield* Effect.promise(() =>
          second.client.callTool({ name: "execute", arguments: { code: GATED_CODE } }),
        );
        const reExecutedText = textOf(reExecuted);
        expect(reExecutedText, "the re-run pauses for approval again").toContain(
          "Execution paused",
        );
        const freshExecutionId = executionIdOf(reExecutedText);
        expect(freshExecutionId, "the re-run mints a different executionId").not.toBe(executionId);
        if (freshExecutionId === undefined) return yield* Effect.die("missing fresh execution id");

        const resumedFresh = yield* Effect.promise(() =>
          second.client.callTool({
            name: "resume",
            arguments: { executionId: freshExecutionId, action: "accept", content: "{}" },
          }),
        );
        expect(resumedFresh.isError, "the fresh approval resumes to completion").not.toBe(true);
        expect(textOf(resumedFresh), "the gated tool's result comes back after approval").toContain(
          APPROVAL_TARGET_TOOL,
        );
      }).pipe(Effect.ensuring(closeQuietly(second)));
    }).pipe(
      Effect.ensuring(
        api.policies
          .remove({ params: { policyId: policy.id }, payload: { owner: "org" } })
          .pipe(Effect.ignore),
      ),
    );
  }),
);

scenario(
  "MCP sessions · a duplicate resume replays the outcome instead of losing the approval",
  {},
  Effect.gen(function* () {
    const target = yield* Target;
    const { client } = yield* Api;
    const mcp = yield* Mcp;
    const identity = yield* target.newIdentity();
    const api = yield* client(coreApi, identity);
    const bearer = yield* mcp.mintBearer(emailOf(identity));

    const policy = yield* api.policies.create({
      payload: { owner: "org", pattern: APPROVAL_TARGET_TOOL, action: "require_approval" },
    });

    yield* Effect.gen(function* () {
      const session = yield* Effect.promise(() => connectClient(target.mcpUrl, bearer));
      yield* Effect.gen(function* () {
        const paused = yield* Effect.promise(() =>
          session.client.callTool({ name: "execute", arguments: { code: GATED_CODE } }),
        );
        const executionId = executionIdOf(textOf(paused));
        expect(executionId, "the paused result carries the executionId").toEqual(
          expect.any(String),
        );

        const resume = () =>
          Effect.promise(() =>
            session.client.callTool({
              name: "resume",
              arguments: { executionId: executionId ?? "", action: "accept", content: "{}" },
            }),
          );

        const first = yield* resume();
        expect(first.isError, "the first resume completes the execution").not.toBe(true);

        // MCP clients retry resume when a response is lost in transit. The
        // retry must replay the same completed outcome — the production
        // failure mode was "No paused execution" seconds after a successful
        // resume.
        const retry = yield* resume();
        expect(retry.isError, "the duplicate resume is not an error").not.toBe(true);
        expect(textOf(retry), "the duplicate resume returns the same completed result").toContain(
          APPROVAL_TARGET_TOOL,
        );
      }).pipe(Effect.ensuring(closeQuietly(session)));
    }).pipe(
      Effect.ensuring(
        api.policies
          .remove({ params: { policyId: policy.id }, payload: { owner: "org" } })
          .pipe(Effect.ignore),
      ),
    );
  }),
);

scenario(
  "MCP sessions · a paused approval survives the client reconnecting and resumes",
  {},
  Effect.gen(function* () {
    const target = yield* Target;
    const { client } = yield* Api;
    const mcp = yield* Mcp;
    const identity = yield* target.newIdentity();
    const api = yield* client(coreApi, identity);
    const bearer = yield* mcp.mintBearer(emailOf(identity));

    // Gate a built-in tool behind human approval; the org is fresh, so the
    // gate affects no other scenario, but remove it anyway on every exit.
    const policy = yield* api.policies.create({
      payload: { owner: "org", pattern: APPROVAL_TARGET_TOOL, action: "require_approval" },
    });

    yield* Effect.gen(function* () {
      const first = yield* Effect.promise(() => connectClient(target.mcpUrl, bearer));
      const sessionId = first.transport.sessionId;
      const paused = yield* Effect.promise(() =>
        first.client.callTool({ name: "execute", arguments: { code: GATED_CODE } }),
      ).pipe(Effect.ensuring(closeQuietly(first)));
      const pausedText = textOf(paused);
      expect(pausedText, "the gated call pauses instead of completing").toContain(
        "Execution paused",
      );
      const executionId = /\bexecutionId:\s*(\S+)/.exec(pausedText)?.[1];
      expect(executionId, "the paused result carries the executionId").toEqual(expect.any(String));

      // The user answers from a NEW client on the same session — the paused
      // execution lives in the session, not the connection.
      const second = yield* Effect.promise(() => connectClient(target.mcpUrl, bearer, sessionId));
      const resumed = yield* Effect.promise(() =>
        second.client.callTool({
          name: "resume",
          arguments: { executionId: executionId ?? "", action: "accept", content: "{}" },
        }),
      ).pipe(Effect.ensuring(closeQuietly(second)));
      expect(resumed.isError, "the resumed execution completes").not.toBe(true);
      expect(textOf(resumed), "the gated tool's result comes back after approval").toContain(
        APPROVAL_TARGET_TOOL,
      );
    }).pipe(
      Effect.ensuring(
        api.policies
          .remove({ params: { policyId: policy.id }, payload: { owner: "org" } })
          .pipe(Effect.ignore),
      ),
    );
  }),
);
