import { describe, it, expect } from "vitest";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { Env } from "../../../src/types.js";
import {
  registerGraphNeighbors,
  GRAPH_NEIGHBORS_WORKFLOW,
} from "../../../src/tools/kbdb_graph.js";
import type { KnowledgeIdentity } from "../../../src/lib/portal-client.js";

/** 服務級憑據（static token / partner key）——既有路徑，行為零變更。 */
const SERVICE: KnowledgeIdentity = { kind: "service" };
/** 有人輸入 Portal 帳密授權的連線——走 cypher 的 portal 資料面。 */
const PORTAL: KnowledgeIdentity = {
  kind: "portal",
  portal: { session: "sess-abc", display_name: "Leo", role: "admin", libraries: ["*"] },
};
/** 本次改版前簽發的舊 token（沒有身分）。 */
const STALE: KnowledgeIdentity = { kind: "stale" };

// ── 假 McpServer：只攔 tool 註冊，抓出 handler 直接呼叫 ─────────────────────────
type ToolHandler = (args: Record<string, unknown>) => Promise<{
  content: { type: string; text: string }[];
  isError?: boolean;
}>;

function makeServer() {
  const tools = new Map<string, { description: string; handler: ToolHandler }>();
  const server = {
    tool(name: string, description: string, _schema: unknown, handler: ToolHandler) {
      tools.set(name, { description, handler });
    },
  };
  return { server: server as unknown as McpServer, tools };
}

// ── 假 CYPHER_EXECUTOR binding：記錄請求、回預設 response ───────────────────────
function makeEnv(respond: (url: URL, init?: RequestInit) => Response) {
  const calls: { url: URL; init?: RequestInit }[] = [];
  const env = {
    CYPHER_EXECUTOR: {
      fetch: async (input: string, init?: RequestInit) => {
        const url = new URL(input);
        calls.push({ url, init });
        return respond(url, init);
      },
    },
  } as unknown as Env;
  return { env, calls };
}

function parseResult(r: { content: { text: string }[] }) {
  return JSON.parse(r.content[0].text) as Record<string, unknown>;
}

describe("kbdb_graph_neighbors: registration", () => {
  it("registers under kbdb_* prefix (D17 KBDB MCP boundary)", () => {
    const { server, tools } = makeServer();
    const { env } = makeEnv(() => new Response("{}"));
    registerGraphNeighbors(server, env, "leo", SERVICE);
    expect(tools.has("kbdb_graph_neighbors")).toBe(true);
    expect(tools.get("kbdb_graph_neighbors")!.description).toContain("graph");
  });
});

describe("kbdb_graph_neighbors: request shape", () => {
  it("hits GET /q/:ns/graph_neighbors with workflow input shape", async () => {
    const { server, tools } = makeServer();
    const { env, calls } = makeEnv(
      () =>
        new Response(
          JSON.stringify({ success: true, start: "Arcrun", depth: 2, neighbors: [], count: 0 }),
          { status: 200 },
        ),
    );
    registerGraphNeighbors(server, env, "leo", SERVICE);
    const res = await tools.get("kbdb_graph_neighbors")!.handler({
      subject: "Arcrun",
      depth: 2,
      kbdb_base: "https://kbdb.example.com",
    });

    expect(calls).toHaveLength(1);
    const url = calls[0].url;
    expect(url.pathname).toBe(`/q/leo/${GRAPH_NEIGHBORS_WORKFLOW}`);
    // workflow input 形狀（registry/examples/graph-neighbors/workflow.yaml）
    expect(url.searchParams.get("node")).toBe("Arcrun");
    expect(url.searchParams.get("depth")).toBe("2");
    expect(url.searchParams.get("template")).toBe("graph_triplet"); // 預設
    expect(url.searchParams.get("namespace")).toBe("leo"); // orgNamespace 注入
    expect(url.searchParams.get("kbdb_base")).toBe("https://kbdb.example.com");
    expect(url.searchParams.get("directed")).toBeNull(); // 預設無向 → 不帶

    const body = parseResult(res);
    expect(body.ok).toBe(true);
  });

  it("depth defaults to 1, directed=true is forwarded, template overridable", async () => {
    const { server, tools } = makeServer();
    const { env, calls } = makeEnv(
      () => new Response(JSON.stringify({ success: true, neighbors: [], count: 0 })),
    );
    registerGraphNeighbors(server, env, "leo", SERVICE);
    await tools.get("kbdb_graph_neighbors")!.handler({
      subject: "A",
      kbdb_base: "https://kbdb.example.com",
      template: "my_triplet",
      directed: true,
    });
    const url = calls[0].url;
    expect(url.searchParams.get("depth")).toBe("1");
    expect(url.searchParams.get("directed")).toBe("true");
    expect(url.searchParams.get("template")).toBe("my_triplet");
  });
});

describe("kbdb_graph_neighbors: honest errors (鐵律：workflow 沒裝不 crash)", () => {
  it("404 → workflow_not_installed with install hints, not a crash", async () => {
    const { server, tools } = makeServer();
    const { env } = makeEnv(
      () => new Response(JSON.stringify({ error: '找不到 workflow "graph_neighbors"' }), { status: 404 }),
    );
    registerGraphNeighbors(server, env, "leo", SERVICE);
    const res = await tools.get("kbdb_graph_neighbors")!.handler({
      subject: "A",
      kbdb_base: "https://kbdb.example.com",
    });
    expect(res.isError).toBe(true);
    const body = parseResult(res);
    expect(body.error_code).toBe("workflow_not_installed");
    expect(JSON.stringify(body.next_actions)).toContain("graph-neighbors/workflow.yaml");
  });

  it("500 execution failure → graph_query_failed, error passed through", async () => {
    const { server, tools } = makeServer();
    const { env } = makeEnv(
      () => new Response(JSON.stringify({ success: false, error: "boom", trace: [] }), { status: 500 }),
    );
    registerGraphNeighbors(server, env, "leo", SERVICE);
    const res = await tools.get("kbdb_graph_neighbors")!.handler({
      subject: "A",
      kbdb_base: "https://kbdb.example.com",
    });
    expect(res.isError).toBe(true);
    const body = parseResult(res);
    expect(body.error_code).toBe("graph_query_failed");
    expect(String(body.human_message)).toContain("boom");
  });

  it("HTTP 200 but workflow-level success:false → not faked as success", async () => {
    const { server, tools } = makeServer();
    const { env } = makeEnv(
      () =>
        new Response(JSON.stringify({ success: false, error: "graph_neighbors 缺 start（node）參數" }), {
          status: 200,
        }),
    );
    registerGraphNeighbors(server, env, "leo", SERVICE);
    const res = await tools.get("kbdb_graph_neighbors")!.handler({
      subject: "A",
      kbdb_base: "https://kbdb.example.com",
    });
    expect(res.isError).toBe(true);
    const body = parseResult(res);
    expect(body.error_code).toBe("graph_query_failed");
  });

  it("empty orgNamespace → no_namespace error, no fetch made", async () => {
    const { server, tools } = makeServer();
    const { env, calls } = makeEnv(() => new Response("{}"));
    registerGraphNeighbors(server, env, "", SERVICE);
    const res = await tools.get("kbdb_graph_neighbors")!.handler({
      subject: "A",
      kbdb_base: "https://kbdb.example.com",
    });
    expect(res.isError).toBe(true);
    expect(parseResult(res).error_code).toBe("no_namespace");
    expect(calls).toHaveLength(0);
  });
});

// ── 2026-08-12：以帳密連線時走登入者的身分（leo：主人查得到的，授權的 AI 就查得到）──
describe("kbdb_graph_neighbors: 登入身分（portal 資料面）", () => {
  it("打 cypher 的 /portal/data/graph/neighbors，且帶的是登入者的 session（不是服務金鑰）", async () => {
    const { server, tools } = makeServer();
    const { env, calls } = makeEnv(
      () =>
        new Response(
          JSON.stringify({
            neighbors: [{ node: "B", predicate: "uses", from: "A", depth: 1 }],
            edges: [],
            count: 1,
          }),
          { status: 200 },
        ),
    );
    registerGraphNeighbors(server, env, "leo", PORTAL);
    const res = await tools.get("kbdb_graph_neighbors")!.handler({ subject: "A", depth: 2 });

    expect(calls).toHaveLength(1);
    expect(calls[0].url.pathname).toBe("/portal/data/graph/neighbors/A");
    expect(calls[0].url.searchParams.get("depth")).toBe("2");
    const headers = new Headers(calls[0].init!.headers as HeadersInit);
    expect(headers.get("Authorization")).toBe("Bearer sess-abc");

    expect(res.isError).toBeUndefined();
    expect((parseResult(res).data as { count: number }).count).toBe(1);
  });

  it("**不需要 kbdb_base**：已經登入過了，不再要第二次「證明你是誰／你的庫在哪」", async () => {
    const { server, tools } = makeServer();
    const { env, calls } = makeEnv(
      () => new Response(JSON.stringify({ neighbors: [], edges: [], count: 0 })),
    );
    registerGraphNeighbors(server, env, "leo", PORTAL);
    const res = await tools.get("kbdb_graph_neighbors")!.handler({ subject: "A" });
    expect(res.isError).toBeUndefined();
    expect(calls).toHaveLength(1);
    // 呼叫端就算硬塞 kbdb_base 也不會被拿去用（server 自己知道要查哪個庫）
    expect(calls[0].url.searchParams.get("kbdb_base")).toBeNull();
  });

  it("session 過期（401）→ 誠實說是登入過期，不說「查不到資料」", async () => {
    const { server, tools } = makeServer();
    const { env } = makeEnv(
      () => new Response(JSON.stringify({ error: "session 無效或已過期" }), { status: 401 }),
    );
    registerGraphNeighbors(server, env, "leo", PORTAL);
    const res = await tools.get("kbdb_graph_neighbors")!.handler({ subject: "A" });
    expect(res.isError).toBe(true);
    expect(parseResult(res).error_code).toBe("session_expired");
  });

  it("無 graph 權限（403）→ 誠實回沒權限，不假裝「沒有關聯」", async () => {
    const { server, tools } = makeServer();
    const { env } = makeEnv(
      () => new Response(JSON.stringify({ error: "無知識圖譜檢視權限" }), { status: 403 }),
    );
    registerGraphNeighbors(server, env, "leo", PORTAL);
    const res = await tools.get("kbdb_graph_neighbors")!.handler({ subject: "A" });
    expect(res.isError).toBe(true);
    expect(parseResult(res).error_code).toBe("forbidden");
  });

  it("舊 token（沒有身分）→ 不偷偷退回服務金鑰那條老路，要求重新連線", async () => {
    const { server, tools } = makeServer();
    const { env, calls } = makeEnv(() => new Response("{}"));
    registerGraphNeighbors(server, env, "leo", STALE);
    const res = await tools.get("kbdb_graph_neighbors")!.handler({ subject: "A" });
    expect(res.isError).toBe(true);
    expect(parseResult(res).error_code).toBe("identity_missing");
    expect(calls).toHaveLength(0); // 一個查詢都沒發出去（fail-closed）
  });
});
