import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

vi.mock("@/lib/auth/mcp-api-token", () => ({
  resolveMcpApiToken: vi.fn(),
}));

vi.mock("@/lib/mcp/session-token", () => ({
  verifyMcpSessionToken: vi.fn(),
}));

vi.mock("@/lib/mcp-governed-execute", () => ({
  governedExecuteTool: vi.fn(),
}));

vi.mock("@/lib/tak/autonomous-work-run", () => ({
  createAutonomousWorkRun: vi.fn(),
  executeAutonomousAgenticLoop: vi.fn(),
  resolveAutonomousWorkAgent: vi.fn(),
  resolveAutonomousWorkTools: vi.fn(),
}));

vi.mock("@/lib/tak/task-records", () => ({
  createTaskMessage: vi.fn(),
}));

vi.mock("@dpf/db", () => ({
  prisma: {
    user: { findUnique: vi.fn() },
    taskRun: { findFirst: vi.fn(), update: vi.fn() },
    agentThread: { upsert: vi.fn() },
    taskMessage: { create: vi.fn() },
  },
}));

import { prisma } from "@dpf/db";
import { resolveMcpApiToken } from "@/lib/auth/mcp-api-token";
import { verifyMcpSessionToken } from "@/lib/mcp/session-token";
import { governedExecuteTool } from "@/lib/mcp-governed-execute";
import {
  createAutonomousWorkRun,
  executeAutonomousAgenticLoop,
  resolveAutonomousWorkAgent,
  resolveAutonomousWorkTools,
} from "@/lib/tak/autonomous-work-run";
import { createTaskMessage } from "@/lib/tak/task-records";
import { GET, POST } from "./route";

const resolveMock = resolveMcpApiToken as unknown as ReturnType<typeof vi.fn>;
const verifySessionMock = verifyMcpSessionToken as unknown as ReturnType<typeof vi.fn>;
const govMock = governedExecuteTool as unknown as ReturnType<typeof vi.fn>;
const userMock = prisma.user.findUnique as unknown as ReturnType<typeof vi.fn>;
const taskRunFindFirstMock = prisma.taskRun.findFirst as unknown as ReturnType<typeof vi.fn>;
const taskRunUpdateMock = prisma.taskRun.update as unknown as ReturnType<typeof vi.fn>;
const agentThreadUpsertMock = prisma.agentThread.upsert as unknown as ReturnType<typeof vi.fn>;
const createRunMock = createAutonomousWorkRun as unknown as ReturnType<typeof vi.fn>;
const executeLoopMock = executeAutonomousAgenticLoop as unknown as ReturnType<typeof vi.fn>;
const resolveAgentMock = resolveAutonomousWorkAgent as unknown as ReturnType<typeof vi.fn>;
const resolveToolsMock = resolveAutonomousWorkTools as unknown as ReturnType<typeof vi.fn>;
const createTaskMessageMock = createTaskMessage as unknown as ReturnType<typeof vi.fn>;

function makeRequest(opts: {
  url?: string;
  method?: string;
  bearer?: string | null;
  sessionJwt?: string | null;
  origin?: string | null;
  body?: unknown;
  forwardedProto?: string;
  forwardedHost?: string;
  hostHeader?: string;
}): Request {
  const headers: Record<string, string> = { "Content-Type": "application/json" };
  if (opts.bearer !== null && opts.bearer !== undefined) {
    headers["Authorization"] = `Bearer ${opts.bearer}`;
  }
  if (opts.sessionJwt !== null && opts.sessionJwt !== undefined) {
    headers["X-MCP-Session"] = opts.sessionJwt;
  }
  if (opts.origin !== null && opts.origin !== undefined) {
    headers["Origin"] = opts.origin;
  }
  if (opts.forwardedProto) headers["X-Forwarded-Proto"] = opts.forwardedProto;
  if (opts.forwardedHost) headers["X-Forwarded-Host"] = opts.forwardedHost;
  if (opts.hostHeader) headers["Host"] = opts.hostHeader;
  return new Request(opts.url ?? "http://localhost:3000/api/mcp/v1", {
    method: opts.method ?? "POST",
    headers,
    body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
  });
}

beforeEach(() => {
  vi.resetAllMocks();
  userMock.mockResolvedValue({
    isSuperuser: true,
    groups: [{ platformRole: { roleId: "HR-000" } }],
  } as never);
  taskRunFindFirstMock.mockResolvedValue(null);
  taskRunUpdateMock.mockResolvedValue({} as never);
  agentThreadUpsertMock.mockResolvedValue({ id: "thread-remote-1" } as never);
  createRunMock.mockResolvedValue({ id: "task-row-1", taskRunId: "TR-MCP-12345678", contextId: "thread-remote-1" } as never);
  resolveAgentMock.mockResolvedValue({
    agentId: "AGT-REMOTE",
    systemPrompt: "You are a governed autonomous coworker.",
    sensitivity: "internal",
    displayName: "Remote Coworker",
  } as never);
  resolveToolsMock.mockResolvedValue({ tools: [], toolsForProvider: [] } as never);
  executeLoopMock.mockResolvedValue({ content: "Done.", executedTools: [] } as never);
  createTaskMessageMock.mockResolvedValue(undefined as never);
});

afterEach(() => {
  vi.resetAllMocks();
});

describe("GET", () => {
  it("returns 405 with Allow header", () => {
    const res = GET();
    expect(res.status).toBe(405);
    expect(res.headers.get("Allow")).toBe("POST");
  });
});

describe("POST — transport guards", () => {
  it("rejects HTTPS-required violations on non-localhost http URLs", async () => {
    const res = await POST(
      makeRequest({
        url: "http://evil.example.com/api/mcp/v1",
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(403);
    const body = await res.json();
    expect(body.error.message).toMatch(/TLS required/i);
  });

  it("allows http on localhost (Mode 1)", async () => {
    resolveMock.mockResolvedValue(null); // pretend token is bad to short-circuit at auth
    const res = await POST(
      makeRequest({
        url: "http://localhost:3000/api/mcp/v1",
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(401); // got past TLS guard, failed at auth
  });

  it("rejects spoofed X-Forwarded-Host on plain HTTP", async () => {
    resolveMock.mockResolvedValue(null);
    const res = await POST(
      makeRequest({
        url: "http://0.0.0.0:3000/api/mcp/v1",
        bearer: "dpfmcp_X",
        forwardedHost: "localhost:3000",
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(403);
  });

  it("allows containerized request when X-Forwarded-Proto is https", async () => {
    resolveMock.mockResolvedValue(null);
    const res = await POST(
      makeRequest({
        url: "http://0.0.0.0:3000/api/mcp/v1",
        bearer: "dpfmcp_X",
        forwardedProto: "https",
        forwardedHost: "portal.example.com",
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(401);
  });

  it("falls back to Host header when no X-Forwarded-Host present", async () => {
    resolveMock.mockResolvedValue(null);
    const res = await POST(
      makeRequest({
        url: "http://0.0.0.0:3000/api/mcp/v1",
        bearer: "dpfmcp_X",
        hostHeader: "127.0.0.1:3000",
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(401);
  });

  it("still rejects http requests on non-localhost hosts (no proxy headers)", async () => {
    const res = await POST(
      makeRequest({
        url: "http://evil.example.com/api/mcp/v1",
        bearer: "dpfmcp_X",
        forwardedHost: "evil.example.com",
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(403);
  });

  // Sandbox→portal MCP traffic on the internal Docker bridge cannot use TLS.
  // MCP_INSECURE_INTERNAL_HOSTS lets the operator opt the internal hostname
  // (`portal`, `host.docker.internal`, …) into the HTTP-allowed set without
  // dropping the gate for the public surface.
  describe("MCP_INSECURE_INTERNAL_HOSTS env opt-in", () => {
    const originalValue = process.env.MCP_INSECURE_INTERNAL_HOSTS;
    afterEach(() => {
      if (originalValue === undefined) {
        delete process.env.MCP_INSECURE_INTERNAL_HOSTS;
      } else {
        process.env.MCP_INSECURE_INTERNAL_HOSTS = originalValue;
      }
    });

    it("allows http on a hostname listed in MCP_INSECURE_INTERNAL_HOSTS", async () => {
      process.env.MCP_INSECURE_INTERNAL_HOSTS = "portal,host.docker.internal";
      resolveMock.mockResolvedValue(null);
      const res = await POST(
        makeRequest({
          url: "http://portal:3000/api/mcp/v1",
          bearer: "dpfmcp_X",
          body: { jsonrpc: "2.0", id: 1, method: "initialize" },
        }),
      );
      expect(res.status).toBe(401); // past transport guard, failed at auth
    });

    it("does not allow hostnames absent from MCP_INSECURE_INTERNAL_HOSTS", async () => {
      process.env.MCP_INSECURE_INTERNAL_HOSTS = "portal";
      const res = await POST(
        makeRequest({
          url: "http://other-internal:3000/api/mcp/v1",
          bearer: "dpfmcp_X",
          body: { jsonrpc: "2.0", id: 1, method: "initialize" },
        }),
      );
      expect(res.status).toBe(403);
    });

    it("ignores empty/whitespace entries in the allowlist", async () => {
      process.env.MCP_INSECURE_INTERNAL_HOSTS = " , portal , ";
      resolveMock.mockResolvedValue(null);
      const res = await POST(
        makeRequest({
          url: "http://portal:3000/api/mcp/v1",
          bearer: "dpfmcp_X",
          body: { jsonrpc: "2.0", id: 1, method: "initialize" },
        }),
      );
      expect(res.status).toBe(401);
    });

    it("does not use X-Forwarded-Host to satisfy MCP_INSECURE_INTERNAL_HOSTS", async () => {
      process.env.MCP_INSECURE_INTERNAL_HOSTS = "portal";
      const res = await POST(
        makeRequest({
          url: "http://evil.example.com/api/mcp/v1",
          bearer: "dpfmcp_X",
          forwardedHost: "portal:3000",
          body: { jsonrpc: "2.0", id: 1, method: "initialize" },
        }),
      );
      expect(res.status).toBe(403);
    });
  });

  it("rejects requests with disallowed Origin", async () => {
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        origin: "https://evil.example.com",
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(403);
  });

  it("allows requests with no Origin (CLI clients)", async () => {
    resolveMock.mockResolvedValue(null);
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        origin: null,
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(401); // past origin guard, fails at auth
  });
});

describe("POST — auth", () => {
  it("returns 401 with WWW-Authenticate when neither Authorization nor X-MCP-Session is present", async () => {
    const res = await POST(
      makeRequest({
        bearer: null,
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(401);
    expect(res.headers.get("WWW-Authenticate")).toMatch(/^Bearer realm="DPF MCP"/);
  });

  it("returns 401 when token resolves to null", async () => {
    resolveMock.mockResolvedValue(null);
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_BAD",
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(401);
    expect(res.headers.get("WWW-Authenticate")).toContain("invalid_token");
  });

  // X-MCP-Session JWT path — used by the Claude CLI execution adapter to
  // authenticate per-call without consuming a persistent dpfmcp_* PAT slot.
  describe("X-MCP-Session JWT", () => {
    it("returns 401 when session JWT verification fails", async () => {
      verifySessionMock.mockResolvedValue(null);
      const res = await POST(
        makeRequest({
          bearer: null,
          sessionJwt: "eyJ.invalid.jwt",
          body: { jsonrpc: "2.0", id: 1, method: "initialize" },
        }),
      );
      expect(res.status).toBe(401);
      expect(res.headers.get("WWW-Authenticate")).toMatch(/invalid or expired MCP session/);
    });

    it("accepts a valid session JWT and reaches initialize", async () => {
      verifySessionMock.mockResolvedValue({
        userId: "u1",
        agentId: "build-specialist",
        threadId: "thread-abc",
        routeContext: "/build",
        scopes: ["backlog_read", "build_plan_write"],
        capability: "write",
      });
      const res = await POST(
        makeRequest({
          bearer: null,
          sessionJwt: "eyJ.valid.jwt",
          body: { jsonrpc: "2.0", id: 1, method: "initialize" },
        }),
      );
      expect(res.status).toBe(200);
      const body = await res.json();
      expect(body.result?.serverInfo?.name).toBe("dpf-platform");
    });

    it("session JWT wins over PAT bearer when both are present", async () => {
      // Session JWT is narrowly scoped per-call. If both headers arrive,
      // we prefer the session so a stale operator PAT in the same shell
      // can't widen the cli-adapter's effective scope.
      verifySessionMock.mockResolvedValue({
        userId: "u-session",
        agentId: "build-specialist",
        threadId: null,
        routeContext: null,
        scopes: ["backlog_read"],
        capability: "read",
      });
      const res = await POST(
        makeRequest({
          bearer: "dpfmcp_should_be_ignored",
          sessionJwt: "eyJ.valid.jwt",
          body: { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "query_backlog", arguments: { limit: 1 } } },
        }),
      );
      // PAT resolver should NOT have been consulted.
      expect(resolveMock).not.toHaveBeenCalled();
      expect(verifySessionMock).toHaveBeenCalledWith("eyJ.valid.jwt");
      expect(res.status).toBe(200);
    });

    it("forwards JWT threadId/routeContext into governedExecuteTool context", async () => {
      verifySessionMock.mockResolvedValue({
        userId: "u1",
        agentId: "build-specialist",
        threadId: "thread-xyz",
        routeContext: "/build",
        scopes: ["backlog_read"],
        capability: "read",
      });
      govMock.mockResolvedValue({
        success: true,
        message: "ok",
        data: { count: 0 },
      });
      const res = await POST(
        makeRequest({
          sessionJwt: "eyJ.valid.jwt",
          body: { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "query_backlog", arguments: { limit: 1 } } },
        }),
      );
      expect(res.status).toBe(200);
      expect(govMock).toHaveBeenCalledTimes(1);
      const callArgs = govMock.mock.calls[0]![0] as Record<string, unknown>;
      expect(callArgs.userId).toBe("u1");
      expect(callArgs.source).toBe("internal-mcp-session");
      const ctx = callArgs.context as Record<string, unknown>;
      expect(ctx.agentId).toBe("build-specialist");
      expect(ctx.threadId).toBe("thread-xyz");
      expect(ctx.routeContext).toBe("/build");
    });

    it("trims whitespace from the X-MCP-Session header value", async () => {
      verifySessionMock.mockResolvedValue({
        userId: "u1",
        agentId: null,
        threadId: null,
        routeContext: null,
        scopes: ["backlog_read"],
        capability: "read",
      });
      const res = await POST(
        makeRequest({
          sessionJwt: "  eyJ.valid.jwt  ",
          body: { jsonrpc: "2.0", id: 1, method: "initialize" },
        }),
      );
      expect(res.status).toBe(200);
      expect(verifySessionMock).toHaveBeenCalledWith("eyJ.valid.jwt");
    });
  });
});

describe("POST — JSON-RPC envelope", () => {
  beforeEach(() => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["backlog_read", "backlog_write"],
      capability: "write",
    });
  });

  it("returns -32700 parse error on invalid JSON body", async () => {
    const res = await POST(
      new Request("http://localhost:3000/api/mcp/v1", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer dpfmcp_X",
        },
        body: "not valid json{",
      }),
    );
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.error.code).toBe(-32700);
  });

  it("returns -32600 invalid request when jsonrpc field is wrong", async () => {
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: { jsonrpc: "1.0", id: 1, method: "initialize" },
      }),
    );
    const body = await res.json();
    expect(body.error.code).toBe(-32600);
  });

  it("returns -32601 method not found for unknown methods", async () => {
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", id: 1, method: "totally_unknown_method" },
      }),
    );
    const body = await res.json();
    expect(body.error.code).toBe(-32601);
  });

  it("returns 202 Accepted with no body for notifications/initialized", async () => {
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", method: "notifications/initialized" }, // no id = notification
      }),
    );
    expect(res.status).toBe(202);
    expect(await res.text()).toBe("");
  });

  it("returns 202 for unknown notifications (no id)", async () => {
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", method: "some/random/notification" },
      }),
    );
    expect(res.status).toBe(202);
  });
});

describe("POST — initialize", () => {
  beforeEach(() => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["backlog_read"],
      capability: "read",
    });
  });

  it("returns the protocol version, server info, and tools capability", async () => {
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", id: 1, method: "initialize" },
      }),
    );
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.jsonrpc).toBe("2.0");
    expect(body.id).toBe(1);
    // No protocolVersion in params → falls back to oldest supported version
    expect(body.result.protocolVersion).toBe("2024-11-05");
    expect(body.result.serverInfo.name).toBe("dpf-platform");
    expect(body.result.capabilities.tools).toBeDefined();
  });

  it("negotiates protocol version — echoes client version when supported", async () => {
    for (const version of ["2024-11-05", "2025-03-26", "2025-11-25"]) {
      const res = await POST(
        makeRequest({
          bearer: "dpfmcp_X",
          body: { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: version } },
        }),
      );
      const body = await res.json();
      expect(body.result.protocolVersion).toBe(version);
    }
  });

  it("negotiates protocol version — falls back when client version is unknown", async () => {
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2099-01-01" } },
      }),
    );
    const body = await res.json();
    expect(body.result.protocolVersion).toBe("2024-11-05");
  });
});

describe("POST — tools/list", () => {
  it("returns only tools the token's scopes can use", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["backlog_read"], // read-only scope
      capability: "read",
    });
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", id: 2, method: "tools/list" },
      }),
    );
    expect(res.status).toBe(200);
    const body = await res.json();
    const toolNames = body.result.tools.map((t: { name: string }) => t.name);
    // Read-scoped tools should appear
    expect(toolNames).toContain("query_backlog");
    expect(toolNames).toContain("list_epics");
    // Write-scoped tools should NOT appear
    expect(toolNames).not.toContain("create_backlog_item");
    expect(toolNames).not.toContain("update_backlog_item_status");
  });

  it("includes annotations on every returned tool", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["backlog_read"],
      capability: "read",
    });
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", id: 3, method: "tools/list" },
      }),
    );
    const body = await res.json();
    for (const tool of body.result.tools) {
      expect(tool.annotations).toBeDefined();
      expect(typeof tool.annotations.readOnlyHint).toBe("boolean");
      expect(typeof tool.annotations.destructiveHint).toBe("boolean");
    }
  });

  // ─── Principles-as-wiki-kind Phase 2 Task 2.8 ───────────────────────────
  describe("principle MCP tools are gated by registry_read scope", () => {
    it("exposes wiki_query and principle_decide to tokens with registry_read", async () => {
      resolveMock.mockResolvedValue({
        tokenId: "tok_p",
        userId: "u1",
        agentId: null,
        scopes: ["registry_read"],
        capability: "read",
      });
      const res = await POST(
        makeRequest({
          bearer: "dpfmcp_X",
          body: { jsonrpc: "2.0", id: 10, method: "tools/list" },
        }),
      );
      expect(res.status).toBe(200);
      const body = await res.json();
      const toolNames = body.result.tools.map((t: { name: string }) => t.name);
      expect(toolNames).toContain("wiki_query");
      expect(toolNames).toContain("principle_decide");
    });

    it("hides wiki_query and principle_decide from tokens without registry_read", async () => {
      resolveMock.mockResolvedValue({
        tokenId: "tok_p",
        userId: "u1",
        agentId: null,
        scopes: ["backlog_read"], // NOT registry_read
        capability: "read",
      });
      const res = await POST(
        makeRequest({
          bearer: "dpfmcp_X",
          body: { jsonrpc: "2.0", id: 11, method: "tools/list" },
        }),
      );
      const body = await res.json();
      const toolNames = body.result.tools.map((t: { name: string }) => t.name);
      expect(toolNames).not.toContain("wiki_query");
      expect(toolNames).not.toContain("principle_decide");
    });

    it("exposes the extended wiki_query input schema (tier, appliesTo, publicOnly, principle in pageKind enum)", async () => {
      resolveMock.mockResolvedValue({
        tokenId: "tok_p",
        userId: "u1",
        agentId: null,
        scopes: ["registry_read"],
        capability: "read",
      });
      const res = await POST(
        makeRequest({
          bearer: "dpfmcp_X",
          body: { jsonrpc: "2.0", id: 12, method: "tools/list" },
        }),
      );
      const body = await res.json();
      const wikiQuery = body.result.tools.find(
        (t: { name: string }) => t.name === "wiki_query",
      );
      expect(wikiQuery).toBeDefined();
      const props = wikiQuery.inputSchema.properties as Record<
        string,
        { enum?: string[] }
      >;
      // pageKind enum now includes "principle"
      expect(props.pageKind.enum).toContain("principle");
      // New principle filter fields are advertised
      expect(props.tier).toBeDefined();
      expect(props.tier.enum).toEqual(["commandment", "core", "contextual"]);
      expect(props.appliesTo).toBeDefined();
      expect(props.appliesTo.enum).toEqual([
        "in_platform_coworker",
        "external_coding_agent",
        "human",
      ]);
      expect(props.publicOnly).toBeDefined();
    });

    it("exposes principle_decide with the documented input shape (context, options, callingPopulation)", async () => {
      resolveMock.mockResolvedValue({
        tokenId: "tok_p",
        userId: "u1",
        agentId: null,
        scopes: ["registry_read"],
        capability: "read",
      });
      const res = await POST(
        makeRequest({
          bearer: "dpfmcp_X",
          body: { jsonrpc: "2.0", id: 13, method: "tools/list" },
        }),
      );
      const body = await res.json();
      const decide = body.result.tools.find(
        (t: { name: string }) => t.name === "principle_decide",
      );
      expect(decide).toBeDefined();
      expect(decide.inputSchema.required).toEqual(
        expect.arrayContaining(["context", "options", "callingPopulation"]),
      );
      expect(decide.annotations.readOnlyHint).toBe(true);
      expect(decide.annotations.destructiveHint).toBe(false);
      expect(decide.annotations.idempotentHint).toBe(true);
    });
  });
});

describe("POST — tools/call", () => {
  it("returns invalid_params when name is missing", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["backlog_read"],
      capability: "read",
    });
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", id: 4, method: "tools/call", params: {} },
      }),
    );
    const body = await res.json();
    expect(body.error.code).toBe(-32602);
  });

  it("returns isError:true with helpful message for unknown tools", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["backlog_read"],
      capability: "read",
    });
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 5,
          method: "tools/call",
          params: { name: "totally_made_up_tool" },
        },
      }),
    );
    const body = await res.json();
    expect(body.result.isError).toBe(true);
    expect(body.result.content[0].text).toContain("Unknown tool");
  });

  it("rejects scope mismatch with isError:true and a clear message (does not call governedExecuteTool)", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["backlog_read"], // no backlog_write
      capability: "read",
    });
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 6,
          method: "tools/call",
          params: { name: "create_backlog_item", arguments: {} },
        },
      }),
    );
    const body = await res.json();
    expect(body.result.isError).toBe(true);
    expect(body.result.structuredContent).toMatchObject({
      error: "insufficient_token_scope",
      requiredScope: "write",
      tokenScope: "read",
      toolName: "create_backlog_item",
    });
    expect(govMock).not.toHaveBeenCalled();
  });

  it("rejects side-effecting calls from read-capable tokens (defense-in-depth)", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["backlog_read", "backlog_write"], // scopes allow it
      capability: "read", // but token capability is read-only
      scope: "read",
    });
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 7,
          method: "tools/call",
          params: { name: "create_backlog_item", arguments: {} },
        },
      }),
    );
    const body = await res.json();
    expect(body.result.isError).toBe(true);
    expect(body.result.content[0].text).toMatch(/write-scoped MCP token/i);
    expect(body.result.structuredContent).toMatchObject({
      error: "insufficient_token_scope",
      requiredScope: "write",
      tokenScope: "read",
      toolName: "create_backlog_item",
    });
    expect(govMock).not.toHaveBeenCalled();
  });

  it("returns requiredScope=write for read tokens that try create_work_capsule", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["work_capsule_write"],
      capability: "read",
      scope: "read",
    });

    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 71,
          method: "tools/call",
          params: {
            name: "create_work_capsule",
            arguments: {
              title: "Token scope test",
              objective: "Verify write-token requirement",
              source: "operator",
              idempotencyKey: "scope-test",
            },
          },
        },
      }),
    );

    const body = await res.json();
    expect(body.result.isError).toBe(true);
    expect(body.result.structuredContent.requiredScope).toBe("write");
    expect(body.result.structuredContent.tokenScope).toBe("read");
    expect(govMock).not.toHaveBeenCalled();
  });

  it("allows write-scoped tokens to call create_work_capsule", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_write",
      userId: "u1",
      agentId: "AGT-CAPSULE",
      scopes: ["work_capsule_write"],
      capability: "write",
      scope: "write",
    });
    govMock.mockResolvedValue({
      success: true,
      message: "Work Capsule WC-SCOPE created.",
      entityId: "WC-SCOPE",
      data: { capsuleId: "WC-SCOPE" },
    });

    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_WRITE",
        body: {
          jsonrpc: "2.0",
          id: 72,
          method: "tools/call",
          params: {
            name: "create_work_capsule",
            arguments: {
              title: "Token scope test",
              objective: "Verify write-token acceptance",
              source: "operator",
              idempotencyKey: "scope-write-test",
            },
          },
        },
      }),
    );

    const body = await res.json();
    expect(body.result.isError).toBe(false);
    expect(body.result.structuredContent).toEqual({ capsuleId: "WC-SCOPE" });
    expect(govMock).toHaveBeenCalledOnce();
    const call = govMock.mock.calls[0]![0];
    expect(call.toolName).toBe("create_work_capsule");
    expect(call.context.apiTokenId).toBe("tok_write");
    expect(call.source).toBe("external-jsonrpc");
  });

  it("dispatches to governedExecuteTool with source=external-jsonrpc and apiTokenId", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_abc",
      userId: "u1",
      agentId: "AGT-100",
      scopes: ["backlog_read"],
      capability: "read",
    });
    govMock.mockResolvedValue({
      success: true,
      message: "ok",
      data: { items: [{ itemId: "BI-X", title: "X" }] },
    });
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 8,
          method: "tools/call",
          params: { name: "list_backlog_items", arguments: { limit: 5 } },
        },
      }),
    );
    expect(res.status).toBe(200);
    expect(govMock).toHaveBeenCalledOnce();
    const call = govMock.mock.calls[0]![0];
    expect(call.toolName).toBe("list_backlog_items");
    expect(call.userId).toBe("u1");
    expect(call.context.agentId).toBe("AGT-100");
    expect(call.context.apiTokenId).toBe("tok_abc");
    expect(call.source).toBe("external-jsonrpc");

    const body = await res.json();
    expect(body.result.isError).toBe(false);
    expect(body.result.content[0].type).toBe("text");
    expect(body.result.content[0].text).toContain("ok");
    // structuredContent mirrors the ToolResult.data
    expect(body.result.structuredContent.items[0].itemId).toBe("BI-X");
  });

  it("returns isError:true and serialized failure when the tool returns success:false", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["backlog_read"],
      capability: "read",
    });
    govMock.mockResolvedValue({
      success: false,
      error: "not_found",
      message: "Item BI-NOPE not found",
    });
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 9,
          method: "tools/call",
          params: { name: "get_backlog_item", arguments: { itemId: "BI-NOPE" } },
        },
      }),
    );
    const body = await res.json();
    expect(body.result.isError).toBe(true);
    expect(body.result.content[0].text).toContain("not_found");
  });
});

describe("POST — ping", () => {
  it("returns empty object for ping", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: null,
      scopes: ["backlog_read"],
      capability: "read",
    });
    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: { jsonrpc: "2.0", id: 10, method: "ping" },
      }),
    );
    const body = await res.json();
    expect(body.result).toEqual({});
  });
});

describe("POST — tasks/submit", () => {
  it("returns invalid_params when idempotencyKey or riskClass is missing", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_x",
      userId: "u1",
      agentId: "AGT-REMOTE",
      scopes: ["backlog_read"],
      capability: "read",
    });

    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 11,
          method: "tasks/submit",
          params: {
            agentId: "AGT-REMOTE",
            routeContext: "/platform/tools/discovery",
            objective: "Investigate discovery backlog.",
            prompt: "Summarize discovery backlog.",
          },
        },
      }),
    );

    const body = await res.json();
    expect(body.error.code).toBe(-32602);
    expect(createRunMock).not.toHaveBeenCalled();
  });

  it("rejects write-risk submissions from read-only tokens before creating a TaskRun", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_read",
      userId: "u1",
      agentId: "AGT-REMOTE",
      scopes: ["backlog_read"],
      capability: "read",
    });

    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 12,
          method: "tasks/submit",
          params: {
            agentId: "AGT-REMOTE",
            routeContext: "/platform/tools/discovery",
            objective: "Prepare a backlog update.",
            prompt: "Create a backlog item if needed.",
            idempotencyKey: "remote-read-denied-1",
            riskClass: "bounded-write",
          },
        },
      }),
    );

    const body = await res.json();
    expect(body.result.isError).toBe(true);
    expect(body.result.content[0].text).toMatch(/read-only/i);
    expect(body.result.structuredContent).toMatchObject({
      error: "insufficient_token_scope",
      requiredScope: "write",
      tokenScope: "read",
    });
    expect(createRunMock).not.toHaveBeenCalled();
    expect(executeLoopMock).not.toHaveBeenCalled();
  });

  it("replays an existing TaskRun for the same idempotencyKey without re-running the coworker", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_write",
      userId: "u1",
      agentId: "AGT-REMOTE",
      scopes: ["backlog_read", "backlog_write"],
      capability: "write",
    });
    taskRunFindFirstMock.mockResolvedValue({
      taskRunId: "TR-MCP-OLD",
      status: "completed",
      progressPayload: { summary: "Already done." },
      a2aMetadata: { idempotencyKey: "remote-replay-1", riskClass: "read" },
    } as never);

    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 13,
          method: "tasks/submit",
          params: {
            agentId: "AGT-REMOTE",
            routeContext: "/platform/tools/discovery",
            objective: "Summarize current state.",
            prompt: "Summarize current state.",
            idempotencyKey: "remote-replay-1",
            riskClass: "read",
          },
        },
      }),
    );

    const body = await res.json();
    expect(body.result.taskRunId).toBe("TR-MCP-OLD");
    expect(body.result.idempotentReplay).toBe(true);
    expect(createRunMock).not.toHaveBeenCalled();
    expect(executeLoopMock).not.toHaveBeenCalled();
  });

  it("creates an external-mcp TaskRun and executes read tasks with token attribution", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_read",
      userId: "u1",
      agentId: "AGT-REMOTE",
      scopes: ["backlog_read"],
      capability: "read",
    });

    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 14,
          method: "tasks/submit",
          params: {
            agentId: "AGT-REMOTE",
            routeContext: "/platform/tools/discovery",
            title: "Remote discovery summary",
            objective: "Summarize discovery backlog.",
            prompt: "Summarize discovery backlog and cite what you used.",
            idempotencyKey: "remote-read-1",
            riskClass: "read",
          },
        },
      }),
    );

    expect(res.status).toBe(200);
    expect(createRunMock).toHaveBeenCalledWith(expect.objectContaining({
      trigger: "external-mcp",
      userId: "u1",
      agentId: "AGT-REMOTE",
      routeContext: "/platform/tools/discovery",
      sourceRef: { kind: "mcp-token", id: "tok_read" },
      metadata: expect.objectContaining({
        idempotencyKey: "remote-read-1",
        riskClass: "read",
        apiTokenId: "tok_read",
      }),
    }));
    expect(resolveToolsMock).toHaveBeenCalledWith(expect.objectContaining({
      mode: "advise",
      externalAccessEnabled: true,
    }));
    expect(executeLoopMock).toHaveBeenCalledWith(expect.objectContaining({
      taskRunId: "TR-MCP-12345678",
      apiTokenId: "tok_read",
      chatHistory: [{ role: "user", content: "Summarize discovery backlog and cite what you used." }],
    }));
    expect(taskRunUpdateMock).toHaveBeenCalledWith(expect.objectContaining({
      where: { taskRunId: "TR-MCP-12345678" },
      data: expect.objectContaining({ status: "completed" }),
    }));
    const body = await res.json();
    expect(body.result.taskRunId).toBe("TR-MCP-12345678");
    expect(body.result.idempotentReplay).toBe(false);
    expect(body.result.requiresApproval).toBe(false);
  });

  it("pauses high-risk submissions before executing tools", async () => {
    resolveMock.mockResolvedValue({
      tokenId: "tok_write",
      userId: "u1",
      agentId: "AGT-REMOTE",
      scopes: ["backlog_read", "backlog_write"],
      capability: "write",
    });

    const res = await POST(
      makeRequest({
        bearer: "dpfmcp_X",
        body: {
          jsonrpc: "2.0",
          id: 15,
          method: "tasks/submit",
          params: {
            agentId: "AGT-REMOTE",
            routeContext: "/platform/tools/discovery",
            objective: "Make broad production changes.",
            prompt: "Make broad production changes.",
            idempotencyKey: "remote-high-risk-1",
            riskClass: "high-risk",
          },
        },
      }),
    );

    const body = await res.json();
    expect(body.result.taskRunId).toBe("TR-MCP-12345678");
    expect(body.result.requiresApproval).toBe(true);
    expect(body.result.status).toBe("input-required");
    expect(taskRunUpdateMock).toHaveBeenCalledWith(expect.objectContaining({
      where: { taskRunId: "TR-MCP-12345678" },
      data: expect.objectContaining({ status: "input-required" }),
    }));
    expect(executeLoopMock).not.toHaveBeenCalled();
  });
});
