import { Hono } from "hono";
import { cors } from "hono/cors";
import { Env } from "./types.js";
import { partnerAuthMiddleware, type AuthPath } from "./middleware/partner-auth.js";
import { handleMcpRequest } from "./mcp-handler.js";
import { resolveKnowledgeIdentity } from "./lib/portal-client.js";
import type { PortalIdentity } from "./oauth/store.js";
import { inspectorHtml } from "./pages/inspector.js";
import { kbdbFetch } from "./lib/kbdb-client.js";
import { registerOAuthRoutes } from "./oauth/routes.js";

const _app = new Hono<{
  Bindings: Env;
  Variables: {
    org_namespace: string;
    partner_token: string;
    // 登入者身分（以帳密走 OAuth 連進來時才有）＋ 這條連線是哪種憑據。
    // 知識面工具（kbdb_*）據此決定走 portal 資料面還是既有 KBDB 直連（見 lib/portal-client.ts）。
    portal?: PortalIdentity;
    auth_path: AuthPath;
  };
}>();

// ── OAuth 2.1 server 路由（掛在 worker 根路徑，非 /mcp）──────────────────────────
// well-known / authorize / token / register 必須在 origin 根，claude.ai 遠端 connector 才發現得到。
// 安全模型見 mcp/OAUTH.md。註冊在 basePath 之前，落在同一份共享 router。
registerOAuthRoutes(_app);

// ── GET /health — 讓「這台跑的是哪一版 MCP」用一條 curl 看得到 ─────────────────────
// 為什麼要有：cypher-executor 早就有 /health（bundle_version + auth_store 探針），
// arcrun-mcp 沒有 ⇒ 要判斷某台實例的 MCP 是哪一代認證，只能打 /authorize 剖 HTML 數欄位
// （ops-facts 2026-08-10 的土法）。那個判準脆弱又難教。
//
// ⚠️ 誠實界定（別把它當世代判準用過頭）：本端點是**這個 commit 之後才有的**，所以
//    「/health 回 404」只代表「比本版舊」，**不代表就是 owner_secret 世代**——
//    現階段判世代仍要看 /authorize 的欄位（一個 owner_secret ＝舊；email+password ＝新）。
//    等這版推到各實例之後，`auth` 欄位才會變成一眼可讀的世代判準。
// 不需認證、不吐任何機密；MCP_BUILD 是部署標記，由各實例 toml [vars] 帶入。
_app.get("/health", (c) => c.json({
  ok: true,
  service: "arcrun-mcp",
  auth: "portal-login",
  build: c.env.MCP_BUILD ?? "unknown",
  oauth_kv: c.env.OAUTH_KV ? "present" : "missing",
}));

const app = _app.basePath('/mcp');

app.use("*", cors({
  origin: "*",
  allowMethods: ["GET", "POST", "OPTIONS"],
  allowHeaders: ["Content-Type", "Authorization"],
  exposeHeaders: ["Content-Type"],
  maxAge: 600,
}));

app.get("/", (c) => c.text("u6u MCP Server is running."));

app.get("/inspector", (c) => {
  return c.html(inspectorHtml);
});

// ── GUI 認證端點 ───────────────────────────────────────────────────────────────

// GET /auth/verify — GUI 登入驗證，重用 partnerAuthMiddleware
app.get("/auth/verify", partnerAuthMiddleware, (c) => {
  const orgNamespace = c.get("org_namespace");
  return c.json({ valid: true, org_namespace: orgNamespace });
});

// ── GUI REST 端點（與 MCP tools 平行） ────────────────────────────────────────

// GET /workflows — 列出 Workflow 清單（GUI 用）
app.get("/workflows", partnerAuthMiddleware, async (c) => {
  const orgNamespace = c.get("org_namespace");
  try {
    const resp = await kbdbFetch(
      c.env,
      `/records/search?template=workflow_metadata&user_id=${encodeURIComponent(orgNamespace)}`
    );
    if (!resp.ok) return c.json({ workflows: [] });
    const data = await resp.json<{ records: Array<{ id: string; slots?: Record<string, unknown> }> }>();
    const workflows = (data.records ?? []).map(r => ({
      id: r.id,
      name: (r.slots?.display_name as string | undefined) ?? (r.slots?.name as string | undefined) ?? r.id,
      last_run: r.slots?.last_run as string | undefined,
      status: r.slots?.status as string | undefined,
      slots: r.slots,
    }));
    return c.json({ workflows });
  } catch {
    return c.json({ workflows: [] });
  }
});

// GET /workflows/:id — 取得單一 Workflow（GUI poll 用）
app.get("/workflows/:id", partnerAuthMiddleware, async (c) => {
  const id = c.req.param("id") ?? '';
  try {
    if (!id) return c.json({ error: "Missing id" }, 400);
    const resp = await kbdbFetch(c.env, `/records/${encodeURIComponent(id)}`);
    if (!resp.ok) return c.json({ error: "Not found" }, 404);
    const data = await resp.json<{ id: string; slots?: Record<string, unknown> }>();
    return c.json({
      id: data.id,
      name: (data.slots?.display_name as string | undefined) ?? data.id,
      slots: data.slots,
    });
  } catch {
    return c.json({ error: "Internal error" }, 500);
  }
});

// POST /action-log — GUI 寫入用戶動作記錄
app.post("/action-log", partnerAuthMiddleware, async (c) => {
  const orgNamespace = c.get("org_namespace");
  try {
    const body = await c.req.json<{
      action_type: string;
      payload?: Record<string, unknown>;
      occurred_at?: string;
    }>();
    const occurred_at = body.occurred_at ?? new Date().toISOString();

    await kbdbFetch(c.env, "/records", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        template_id: "tpl-action-log",
        user_id: orgNamespace,
        slots: {
          org_namespace: orgNamespace,
          action_type: body.action_type,
          payload: JSON.stringify(body.payload ?? {}),
          occurred_at,
        },
      }),
    });
    return c.json({ ok: true });
  } catch {
    return c.json({ ok: false }, 500);
  }
});

// ── Prototype Pages REST 端點 ─────────────────────────────────────────────────

// GET /prototype-pages — 列出 Prototype Pages
app.get("/prototype-pages", partnerAuthMiddleware, async (c) => {
  const orgNamespace = c.get("org_namespace");
  try {
    const resp = await kbdbFetch(
      c.env,
      `/records/search?template=tpl-page-block&user_id=${encodeURIComponent(orgNamespace)}`
    );
    if (!resp.ok) return c.json({ pages: [] });
    const data = await resp.json<{ records: Array<{ id: string; slots?: Record<string, unknown> }> }>();
    const pages = (data.records ?? []).map(r => ({
      id: r.id,
      page_name: (r.slots?.page_name as string | undefined) ?? 'Untitled',
      components_json: (r.slots?.components_json as string | undefined) ?? '[]',
      last_edited_by: (r.slots?.last_edited_by as string | undefined) ?? 'gui',
      last_edited_at: (r.slots?.last_edited_at as string | undefined) ?? '',
      status: (r.slots?.status as string | undefined) ?? 'draft',
    }));
    return c.json({ pages });
  } catch {
    return c.json({ pages: [] });
  }
});

// POST /prototype-pages — 建立新 Prototype Page
app.post("/prototype-pages", partnerAuthMiddleware, async (c) => {
  const orgNamespace = c.get("org_namespace");
  try {
    const body = await c.req.json<{ page_name?: string }>();
    const page_name = body.page_name ?? 'Untitled';
    const now = new Date().toISOString();

    const resp = await kbdbFetch(c.env, "/records", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        template_id: "tpl-page-block",
        user_id: orgNamespace,
        slots: {
          page_name,
          org_namespace: orgNamespace,
          components_json: '[]',
          last_edited_by: 'gui',
          last_edited_at: now,
          status: 'draft',
        },
      }),
    });
    if (!resp.ok) return c.json({ error: "Failed to create" }, 500);
    const data = await resp.json<{ id: string; slots?: Record<string, unknown> }>();
    return c.json({
      id: data.id,
      page_name,
      components_json: '[]',
      last_edited_by: 'gui',
      last_edited_at: now,
      status: 'draft',
    }, 201);
  } catch {
    return c.json({ error: "Internal error" }, 500);
  }
});

// GET /prototype-pages/:id — 取得單一 Prototype Page
app.get("/prototype-pages/:id", partnerAuthMiddleware, async (c) => {
  const id = c.req.param("id") ?? '';
  try {
    if (!id) return c.json({ error: "Missing id" }, 400);
    const resp = await kbdbFetch(c.env, `/records/${encodeURIComponent(id)}`);
    if (!resp.ok) return c.json({ error: "Not found" }, 404);
    const data = await resp.json<{ id: string; slots?: Record<string, unknown> }>();
    return c.json({
      id: data.id,
      page_name: (data.slots?.page_name as string | undefined) ?? 'Untitled',
      components_json: (data.slots?.components_json as string | undefined) ?? '[]',
      last_edited_by: (data.slots?.last_edited_by as string | undefined) ?? 'gui',
      last_edited_at: (data.slots?.last_edited_at as string | undefined) ?? '',
      status: (data.slots?.status as string | undefined) ?? 'draft',
    });
  } catch {
    return c.json({ error: "Internal error" }, 500);
  }
});

// PUT /prototype-pages/:id — 儲存 Prototype Page
app.put("/prototype-pages/:id", partnerAuthMiddleware, async (c) => {
  const id = c.req.param("id") ?? '';
  try {
    if (!id) return c.json({ error: "Missing id" }, 400);
    const body = await c.req.json<{
      components_json?: string;
      page_name?: string;
    }>();
    const now = new Date().toISOString();
    const slots: Record<string, unknown> = {
      last_edited_by: 'gui',
      last_edited_at: now,
    };
    if (body.components_json !== undefined) slots.components_json = body.components_json;
    if (body.page_name !== undefined) slots.page_name = body.page_name;

    const resp = await kbdbFetch(c.env, `/records/${encodeURIComponent(id)}`, {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ slots }),
    });
    if (!resp.ok) return c.json({ error: "Failed to save" }, 500);
    return c.json({ ok: true });
  } catch {
    return c.json({ error: "Internal error" }, 500);
  }
});

// ── MCP 端點 ──────────────────────────────────────────────────────────────────

app.options("/mcp", (c) => {
  return new Response(null, {
    status: 204,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "POST, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
  });
});

app.post("/", partnerAuthMiddleware, async (c) => {
  const orgNamespace = c.get("org_namespace");
  const partnerToken = c.get("partner_token");
  const identity = resolveKnowledgeIdentity(c.get("auth_path"), c.get("portal"));
  return handleMcpRequest(c.req.raw, c.env, orgNamespace, partnerToken, identity);
});

// 輸出根 app（_app）：與 basePath('/mcp') 的 app 共享同一份 router，故 OAuth 根路由與
// /mcp 路由都能被分派。（若輸出 app 則根路徑的 well-known 分派行為依賴 basePath 細節，改輸出 _app 明確。）
export default _app;
