/**
 * KBDB graph 查詢 MCP 薄殼（issue #68，D17 KBDB MCP 面）
 *
 * rule 07 §5（薄殼鐵律）：能力長在 workflow（graph_neighbors，registry/examples/graph-neighbors），
 * MCP 只做介面轉換 + 暴露，無業務邏輯——不碰 D1、不動 workflow 引擎本體。
 *
 * D17「KBDB MCP＝RAG 套餐」三模式：關鍵字（kbdb_search keyword）／語義（kbdb_search semantic）
 * ／圖（本檔）。本檔補上 MCP 面缺的第三模式：關係遍歷（1-hop/N-hop 鄰居）。
 *
 * 走 #28 地基的同步查詢端點（cypher-executor webhooks-named.ts）：
 *   GET /q/:ns/:name — 同步執行 workflow，直接回「最終節點輸出」本身（200 直出，非 202、非信封）。
 * 本工具打 GET /q/{orgNamespace}/graph_neighbors（經既有 CYPHER_EXECUTOR service binding，
 * 不新增 binding），namespace 用 MCP token 解析出的 orgNamespace（與 whoami 同源）。
 *
 * workflow 未部署（用戶沒裝 graph_neighbors）→ 404 → 誠實回錯誤＋怎麼裝，不 crash（鐵律）。
 *
 * graph_neighbors workflow 的 input 形狀（registry/examples/graph-neighbors/workflow.yaml）：
 *   node（起點）、depth（預設 1）、template（triplet template 名）、namespace（owner_id）、
 *   kbdb_base（呼叫者自己的 KBDB 對外 URL——workflow 刻意不寫死任何一家的庫）、directed。
 */

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import type { Env } from "../types.js";
import { cypherFetch, errorResponse, successResponse } from "../lib/cypher-client.js";
import {
  portalFetch,
  portalError,
  staleIdentityError,
  type KnowledgeIdentity,
} from "../lib/portal-client.js";

/** graph 查詢 workflow 名（與 registry/examples/graph-neighbors/workflow.yaml 的 name 一致）。 */
export const GRAPH_NEIGHBORS_WORKFLOW = "graph_neighbors";

/** 未裝 workflow 時的安裝指引（404 與內層錯誤共用，訊息一致）。 */
const INSTALL_HINTS = [
  `graph 查詢由「${GRAPH_NEIGHBORS_WORKFLOW}」workflow 承載（薄殼設計，MCP 本身不算圖）`,
  "安裝：拿 registry/examples/graph-neighbors/workflow.yaml，call arcrun_push_workflow 部署（name 必須是 graph_neighbors）",
  "或用 CLI：acr push registry/examples/graph-neighbors/workflow.yaml",
];

/** 註冊全部 KBDB graph 查詢工具（issue #68）。 */
export function registerAllKbdbGraphTools(
  server: McpServer,
  env: Env,
  orgNamespace: string,
  identity: KnowledgeIdentity,
) {
  registerGraphNeighbors(server, env, orgNamespace, identity);
  // graph_traverse：repo 內目前只有 graph-neighbors 有 workflow 定義（registry/examples/），
  // traverse 尚無可對齊的 input 形狀 → 不猜、不過度工程；等 workflow 進 registry 再加薄殼。
}

/**
 * kbdb_graph_neighbors — knowledge graph 1-hop/N-hop 鄰居查詢。
 * 薄殼調 GET /q/{ns}/graph_neighbors，結果（最終節點輸出）原樣回給 MCP client。
 */
export function registerGraphNeighbors(
  server: McpServer,
  env: Env,
  orgNamespace: string,
  identity: KnowledgeIdentity,
) {
  server.tool(
    "kbdb_graph_neighbors",
    "knowledge graph 鄰居查詢（1-hop/N-hop 關係遍歷）：給一個節點名，沿 KBDB triplet" +
      "（subject-predicate-object）記錄做 BFS，回傳 depth 跳內的鄰居清單" +
      "（[{node, predicate, from, depth}]）。與 kbdb_search（關鍵字/語義）互補：" +
      "找「跟 X 有關係的東西」用本工具，找「內容含關鍵字的東西」用 kbdb_search。" +
      "需要 namespace 裡已部署 graph_neighbors workflow（沒裝會回安裝指引，不會 crash）。",
    {
      subject: z.string().min(1).describe(
        "起點節點名（graph triplet 的 subject/object 值），如 'Arcrun'",
      ),
      depth: z.number().int().min(1).max(10).optional().describe(
        "最大跳數（N-hop），預設 1（只看直接鄰居）",
      ),
      kbdb_base: z.string().min(1).optional().describe(
        "【登入身分下不需要，留空即可】你自己部署的 KBDB 對外 base URL。" +
          "以帳密連線的 MCP 由 server 端自己知道要查哪個庫——不必、也不該由你指定" +
          "（指定了也不會採用）。只有服務級 token（static token / partner key）連線時才需要填。",
      ),
      template: z.string().optional().describe(
        "triplet 記錄的 template 名，預設 'graph_triplet'（以實際部署的 kbdb-graph-plugin " +
          "triplet template 為準，不確定可 kbdb_list_templates 查）",
      ),
      directed: z.boolean().optional().describe(
        "true=只走 subject→object 有向邊；預設 false（把 triplet 當雙向邊，無向鄰居）",
      ),
    },
    async ({ subject, depth, kbdb_base, template, directed }) => {
      if (identity.kind === "stale") return staleIdentityError();

      // ── 登入身分：走 cypher 的 portal 資料面（與人類在 portal 按「關聯」同一支端點）──
      // 那支已經有 D-4 graph 粗閘（沒有 graph 來源庫權限 → 403），也已經處理好
      // 「這台實例沒裝 graph plugin 就改用 tenant 的 graph_neighbors workflow」的兩條路。
      // ⇒ MCP 不必要 kbdb_base、不必知道租戶、不必再認證一次。
      if (identity.kind === "portal") {
        try {
          const res = await portalFetch(
            env,
            identity.portal.session,
            `/portal/data/graph/neighbors/${encodeURIComponent(subject)}`,
            { query: { depth: depth ?? 1 } },
          );
          if (!res.ok) return portalError(res, `查「${subject}」的鄰居`);
          const out = (await res.json().catch(() => null)) as
            | { neighbors?: unknown[]; edges?: unknown[]; count?: number }
            | null;
          return successResponse(out, [
            `${out?.count ?? 0} 個鄰居（depth 上限 ${depth ?? 1}）`,
            "count=0 且不確定資料有沒有進圖：kbdb_query(template='triplet') 看三元組記錄",
            "找關鍵字內容改用 kbdb_search；取單筆全文用 kbdb_get_record",
            "查詢範圍＝你這個帳號被授權的知識庫（與 portal 網頁上的關聯檢視一致）",
          ]);
        } catch (e) {
          return errorResponse("internal_error", e instanceof Error ? e.message : String(e), ["稍後重試"]);
        }
      }

      // ── 服務級憑據：既有路徑（打 /q/:ns/graph_neighbors workflow），行為零變更 ──
      if (!kbdb_base) {
        return errorResponse(
          "kbdb_base_required",
          "以服務級 token 連線時，graph 查詢需要 kbdb_base（你自己 KBDB 的對外 URL）",
          ["改用帳密連線（OAuth）則不需要此參數", "或帶上 kbdb_base 再試一次"],
        );
      }
      if (!orgNamespace) {
        return errorResponse(
          "no_namespace",
          "此 MCP 連線沒有解析出 namespace，無法定位 graph workflow",
          ["call arcrun_whoami 確認身份", "確認 MCP token / OAuth 設定正確"],
        );
      }
      try {
        // input 形狀對齊 graph_neighbors workflow（node/depth/template/namespace/kbdb_base/directed）。
        const query: Record<string, string | number> = {
          node: subject,
          depth: depth ?? 1,
          template: template ?? "graph_triplet",
          namespace: orgNamespace,
          kbdb_base,
        };
        if (directed) query.directed = "true";

        // /q/:ns/:name：namespace 走 path（cypher opaque-key 模型，orgNamespace 即分區 key），
        // 走既有 CYPHER_EXECUTOR service binding（cypherFetch），不新增 binding。
        const res = await cypherFetch(
          env,
          `/q/${encodeURIComponent(orgNamespace)}/${GRAPH_NEIGHBORS_WORKFLOW}`,
          { apiKey: orgNamespace, query },
        );

        if (res.status === 404) {
          // workflow 沒裝 → 誠實 + 給安裝路徑（鐵律：不 crash、不假綠）。
          return errorResponse(
            "workflow_not_installed",
            `namespace「${orgNamespace}」尚未部署 ${GRAPH_NEIGHBORS_WORKFLOW} workflow，graph 查詢無法使用`,
            INSTALL_HINTS,
          );
        }

        const bodyText = await res.text();
        let data: unknown = null;
        try {
          data = bodyText ? JSON.parse(bodyText) : null;
        } catch {
          data = bodyText;
        }

        if (!res.ok) {
          // 409=paused（無法同步查詢）、413=輸出過大、500=執行失敗，端點都回 {error,...}。
          const err = (data ?? {}) as { error?: string };
          return errorResponse(
            "graph_query_failed",
            `graph 查詢失敗 HTTP ${res.status}: ${err.error ?? "unknown"}`,
            [
              "確認 kbdb_base 是你自己 KBDB 的對外 URL 且可被 cypher-executor fetch（1042 陷阱：優先 custom domain）",
              "確認 template 名對得上實際 triplet template（kbdb_list_templates）",
              "call arcrun_list_recent_executions('graph_neighbors') 看 trace",
            ],
            typeof data === "string" ? data : JSON.stringify(data),
          );
        }

        // 200 = workflow 跑完，body 即最終節點輸出本身。但 workflow 內部仍可能回
        // { success:false, error }（如缺參數）——不把它假裝成成功結果。
        const out = data as { success?: boolean; error?: string; count?: number } | null;
        if (out && typeof out === "object" && out.success === false) {
          return errorResponse(
            "graph_query_failed",
            out.error ?? "graph_neighbors workflow 回報失敗",
            ["確認 subject 非空", "確認參數形狀（depth 為正整數）"],
            JSON.stringify(out),
          );
        }

        // 原樣回給 MCP client（薄殼：不加工、不重排）。
        return successResponse(out, [
          `${out?.count ?? 0} 個鄰居（depth 上限 ${depth ?? 1}）`,
          "count=0 且不確定資料有沒有進圖：kbdb_query(template='graph_triplet') 看 triplet 記錄",
          "找關鍵字內容改用 kbdb_search；取單筆全文用 kbdb_get_record",
        ]);
      } catch (e) {
        return errorResponse("internal_error", e instanceof Error ? e.message : String(e), ["稍後重試"]);
      }
    },
  );
}
