/**
 * repo-mcp-server v1.0
 * 光湖·仓库执行手 — 给 Notion 人格体操作代码仓库的 MCP 桥梁
 *
 * 霜砚(AG-SY-01) → 通过此 MCP 工具 → 直接读写代码仓库
 * 自己做 · 零翻译成本
 *
 * 协议: MCP Streamable HTTP (stateless)
 * 部署: CVM (guanghulab.com)
 * 认证: Bearer Token (REPO_MCP_SECRET)
 * 后端: Gitea API (guanghulab.com/api/v1)
 *
 * 版权: 国作登字-2026-A-00037559
 * 主权: TCS-0002∞ 冰朔 | 守护: 铸渊 ICE-GL-ZY001
 */

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
import { timingSafeEqual } from 'crypto';
import { z } from 'zod';

const app = express();
app.use(express.json({ limit: '10mb' }));

const PORT = process.env.REPO_MCP_PORT || 3903;
const MCP_SECRET = process.env.REPO_MCP_SECRET;
const GITEA_TOKEN = process.env.GITEA_TOKEN;
const GITEA_URL = process.env.GITEA_URL || 'https://guanghulab.com';
const REPO_OWNER = process.env.REPO_OWNER || 'bingshuo';
const REPO_NAME = process.env.REPO_NAME || 'guanghulab';
const DEFAULT_BRANCH = process.env.REPO_DEFAULT_BRANCH || 'notion/shuangyan'; // Notion 人格体专属分支

// ─── 常量时间比较（防时序攻击） ───
function safeCompare(a, b) {
  if (typeof a !== 'string' || typeof b !== 'string') return false;
  if (a.length !== b.length) return false;
  try {
    return timingSafeEqual(Buffer.from(a, 'utf-8'), Buffer.from(b, 'utf-8'));
  } catch {
    return false;
  }
}

// ─── 认证中间件 ───
function auth(req, res, next) {
  if (!MCP_SECRET) {
    const ip = req.ip || req.connection?.remoteAddress || '';
    const isLocal = ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
    if (isLocal) return next();
    return res.status(403).json({
      jsonrpc: '2.0',
      error: { code: -32001, message: '未配置 REPO_MCP_SECRET，仅允许本地访问(127.0.0.1)' }
    });
  }
  const token = req.headers.authorization;
  if (!token || !safeCompare(token, `Bearer ${MCP_SECRET}`)) {
    return res.status(401).json({ jsonrpc: '2.0', error: { code: -32001, message: '未授权' } });
  }
  next();
}

// ─── Gitea API 工具函数 ───
const GITEA_API = `${GITEA_URL}/api/v1`;

async function giteaFetch(path, options = {}) {
  const url = `${GITEA_API}${path}`;
  const res = await fetch(url, {
    ...options,
    headers: {
      'Authorization': `token ${GITEA_TOKEN}`,
      'Content-Type': 'application/json',
      ...(options.headers || {})
    }
  });
  const text = await res.text();
  let data;
  try { data = JSON.parse(text); } catch { data = text; }
  if (!res.ok) {
    return { error: true, status: res.status, message: typeof data === 'string' ? data : data.message || JSON.stringify(data) };
  }
  return data;
}

async function repoFetch(path, options = {}) {
  return giteaFetch(`/repos/${REPO_OWNER}/${REPO_NAME}${path}`, options);
}

// ─── 注册所有工具 ───
function registerTools(server) {

  // 1. repo_read
  server.tool(
    'repo_read',
    '读取仓库文件内容',
    { path: z.string().describe('文件路径，如 brain/progress.json'), branch: z.string().optional().describe('分支，默认 notion/shuangyan') },
    async ({ path, branch }) => {
      const data = await repoFetch(`/contents/${encodeURIComponent(path)}?ref=${branch || DEFAULT_BRANCH}`);
      if (data.error) return { content: [{ type: 'text', text: `❌ 读取失败: ${data.message}` }] };
      const content = Buffer.from(data.content, 'base64').toString('utf-8');
      return { content: [{ type: 'text', text: content }] };
    }
  );

  // 2. repo_write
  server.tool(
    'repo_write',
    '写入或更新仓库文件（直接提交到分支）',
    {
      path: z.string().describe('文件路径'),
      content: z.string().describe('文件内容'),
      message: z.string().describe('提交信息'),
      branch: z.string().optional().describe('目标分支，默认 notion/shuangyan')
    },
    async ({ path, content, message, branch }) => {
      const ref = branch || DEFAULT_BRANCH;
      let sha = null;
      const existing = await repoFetch(`/contents/${encodeURIComponent(path)}?ref=${ref}`);
      if (!existing.error) sha = existing.sha;

      const body = { content: Buffer.from(content).toString('base64'), message, branch: ref };
      if (sha) body.sha = sha;

      const result = await repoFetch(`/contents/${encodeURIComponent(path)}`, {
        method: 'PUT',
        body: JSON.stringify(body)
      });
      if (result.error) return { content: [{ type: 'text', text: `❌ 写入失败: ${result.message}` }] };
      return { content: [{ type: 'text', text: `✅ 已写入 ${path}\n提交: ${result.commit?.sha?.slice(0, 7) || '完成'}` }] };
    }
  );

  // 3. repo_delete
  server.tool(
    'repo_delete',
    '删除仓库文件',
    {
      path: z.string().describe('文件路径'),
      message: z.string().describe('提交信息'),
      branch: z.string().optional().describe('分支，默认 notion/shuangyan')
    },
    async ({ path, message, branch }) => {
      const ref = branch || DEFAULT_BRANCH;
      const existing = await repoFetch(`/contents/${encodeURIComponent(path)}?ref=${ref}`);
      if (existing.error) return { content: [{ type: 'text', text: `❌ 文件不存在: ${path}` }] };

      const result = await repoFetch(`/contents/${encodeURIComponent(path)}`, {
        method: 'DELETE',
        body: JSON.stringify({ sha: existing.sha, message, branch: ref })
      });
      if (result.error) return { content: [{ type: 'text', text: `❌ 删除失败: ${result.message}` }] };
      return { content: [{ type: 'text', text: `✅ 已删除 ${path}` }] };
    }
  );

  // 4. repo_list
  server.tool(
    'repo_list',
    '列出仓库目录内容',
    { path: z.string().optional().describe('目录路径，默认根目录'), branch: z.string().optional().describe('分支，默认 notion/shuangyan') },
    async ({ path, branch }) => {
      const dir = path || '';
      const urlPath = dir ? `/contents/${encodeURIComponent(dir)}` : '/contents';
      const data = await repoFetch(`${urlPath}?ref=${branch || DEFAULT_BRANCH}`);
      if (data.error) return { content: [{ type: 'text', text: `❌ 列出失败: ${data.message}` }] };
      if (!Array.isArray(data)) {
        return { content: [{ type: 'text', text: `📄 ${dir || '/'} 是一个文件，请用 repo_read 读取` }] };
      }
      let output = `📂 ${dir || '/'}\n`;
      for (const item of data) {
        output += `${item.type === 'dir' ? '📁' : '📄'} ${item.name}\n`;
      }
      return { content: [{ type: 'text', text: output }] };
    }
  );

  // 5. repo_search
  server.tool(
    'repo_search',
    '在仓库中搜索代码',
    { query: z.string().describe('搜索关键词') },
    async ({ query }) => {
      const data = await giteaFetch(`/repos/${REPO_OWNER}/${REPO_NAME}/search?q=${encodeURIComponent(query)}`);
      if (data.error) return { content: [{ type: 'text', text: `❌ 搜索失败: ${data.message}` }] };
      const results = Array.isArray(data) ? data : data.results || [];
      if (results.length === 0) return { content: [{ type: 'text', text: `🔍 未找到匹配 "${query}"` }] };
      let output = `🔍 找到 ${results.length} 个结果:\n\n`;
      for (const r of results.slice(0, 30)) {
        output += `📄 ${r.filename || r.path || r.name}\n`;
      }
      return { content: [{ type: 'text', text: output }] };
    }
  );

  // 6. repo_branch_list
  server.tool(
    'repo_branch_list',
    '列出所有分支',
    {},
    async () => {
      const data = await repoFetch('/branches');
      if (data.error) return { content: [{ type: 'text', text: `❌ 获取分支失败: ${data.message}` }] };
      let output = '🌿 分支列表:\n';
      for (const b of data) {
        output += `- ${b.name} (${(b.commit?.sha || '').slice(0, 7)})\n`;
      }
      return { content: [{ type: 'text', text: output }] };
    }
  );

  // 7. repo_branch_create
  server.tool(
    'repo_branch_create',
    '创建新分支',
    {
      branch: z.string().describe('新分支名称'),
      from: z.string().optional().describe('来源分支，默认 main')
    },
    async ({ branch, from }) => {
      const shaData = await repoFetch(`/git/refs/heads/${from || 'main'}`);
      if (shaData.error) return { content: [{ type: 'text', text: `❌ 获取来源分支失败: ${shaData.message}` }] };

      const result = await giteaFetch(`/repos/${REPO_OWNER}/${REPO_NAME}/git/refs`, {
        method: 'POST',
        body: JSON.stringify({ ref: `refs/heads/${branch}`, sha: shaData.object.sha })
      });
      if (result.error) return { content: [{ type: 'text', text: `❌ 创建分支失败: ${result.message}` }] };
      return { content: [{ type: 'text', text: `✅ 已创建分支 ${branch} (来自 ${from || 'main'})` }] };
    }
  );

  // 8. repo_pr_create
  server.tool(
    'repo_pr_create',
    '创建 Pull Request',
    {
      title: z.string().describe('PR 标题'),
      body: z.string().optional().describe('PR 描述'),
      head: z.string().describe('源分支'),
      base: z.string().optional().describe('目标分支，默认 notion/shuangyan')
    },
    async ({ title, body, head, base }) => {
      const result = await repoFetch('/pulls', {
        method: 'POST',
        body: JSON.stringify({ title, body: body || '', head, base: base || DEFAULT_BRANCH })
      });
      if (result.error) return { content: [{ type: 'text', text: `❌ 创建 PR 失败: ${result.message}` }] };
      return {
        content: [{
          type: 'text',
          text: `✅ PR #${result.number} 已创建\n标题: ${result.title}\n链接: ${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}/pulls/${result.number}`
        }]
      };
    }
  );

  // 9. repo_pr_list
  server.tool(
    'repo_pr_list',
    '列出 Pull Request',
    { state: z.string().optional().describe('状态: open/closed/all，默认 open') },
    async ({ state }) => {
      const data = await repoFetch(`/pulls?state=${state || 'open'}&limit=20`);
      if (data.error) return { content: [{ type: 'text', text: `❌ 获取 PR 失败: ${data.message}` }] };
      if (data.length === 0) return { content: [{ type: 'text', text: '📭 没有 PR' }] };
      let output = `📋 PR 列表 (${state || 'open'}):\n\n`;
      for (const pr of data) {
        output += `#${pr.number} [${pr.state}] ${pr.title}\n  源: ${pr.head?.label} → ${pr.base?.label}\n  作者: ${pr.user?.login}\n\n`;
      }
      return { content: [{ type: 'text', text: output }] };
    }
  );

  // 10. repo_workflow_run
  server.tool(
    'repo_workflow_run',
    '触发 Forgejo Actions 工作流',
    {
      workflow: z.string().describe('工作流文件名，如 deploy-brand-ui.yaml'),
      inputs: z.string().optional().describe('JSON 格式的输入参数')
    },
    async ({ workflow, inputs }) => {
      const body = {};
      if (inputs) {
        try { body.inputs = JSON.parse(inputs); }
        catch { return { content: [{ type: 'text', text: '❌ inputs 不是有效的 JSON 格式' }] }; }
      }
      const result = await repoFetch(`/actions/workflows/${encodeURIComponent(workflow)}/dispatches`, {
        method: 'PUT',
        body: JSON.stringify(body)
      });
      if (result.error) return { content: [{ type: 'text', text: `❌ 触发工作流失败: ${result.message}` }] };
      return { content: [{ type: 'text', text: `✅ 已触发工作流 ${workflow}\n查看: ${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}/actions` }] };
    }
  );

  // 11. repo_status
  server.tool(
    'repo_status',
    '查看仓库概览状态',
    {},
    async () => {
      const repo = await repoFetch('');
      if (repo.error) return { content: [{ type: 'text', text: `❌ 获取仓库状态失败: ${repo.message}` }] };
      return {
        content: [{
          type: 'text',
          text: [
            `📊 仓库状态: ${REPO_OWNER}/${REPO_NAME}`,
            `━━━━━━━━━━━━━━━━━━━━`,
            `描述: ${repo.description || '无'}`,
            `默认分支: ${repo.default_branch}`,
            `大小: ${((repo.size || 0) / 1024).toFixed(1)} MB`,
            `最后更新: ${repo.updated_at || '未知'}`,
            `━━━━━━━━━━━━━━━━━━━━`,
            `🔗 ${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}`
          ].join('\n')
        }]
      };
    }
  );

  // 12. repo_log
  server.tool(
    'repo_log',
    '查看最近提交历史',
    {
      limit: z.number().optional().describe('数量，默认 10'),
      branch: z.string().optional().describe('分支，默认 notion/shuangyan')
    },
    async ({ limit, branch }) => {
      const data = await repoFetch(`/commits?sha=${branch || DEFAULT_BRANCH}&limit=${limit || 10}`);
      if (data.error) return { content: [{ type: 'text', text: `❌ 获取提交历史失败: ${data.message}` }] };
      let output = `📜 最近提交 (${branch || DEFAULT_BRANCH}):\n\n`;
      for (const c of data) {
        const date = c.commit?.committer?.date?.slice(0, 10) || '';
        const msg = (c.commit?.message || '').split('\n')[0];
        output += `[${(c.sha || '').slice(0, 7)}] ${date} ${msg}\n`;
      }
      return { content: [{ type: 'text', text: output }] };
    }
  );

  // 13. repo_file_tree
  server.tool(
    'repo_file_tree',
    '递归列出目录结构（探索仓库骨架）',
    {
      path: z.string().optional().describe('起始路径，默认 brain'),
      branch: z.string().optional().describe('分支，默认 notion/shuangyan'),
      depth: z.number().optional().describe('递归深度，默认 2，最大 4')
    },
    async ({ path, branch, depth }) => {
      const startPath = path || 'brain';
      const maxDepth = Math.min(depth || 2, 4);
      const ref = branch || DEFAULT_BRANCH;

      async function walk(dirPath, currentDepth) {
        if (currentDepth > maxDepth) return '';
        const urlPath = dirPath ? `/contents/${encodeURIComponent(dirPath)}` : '/contents';
        const data = await repoFetch(`${urlPath}?ref=${ref}`);
        if (!Array.isArray(data)) return '';
        let result = '';
        for (const item of data) {
          const indent = '  '.repeat(currentDepth);
          if (item.type === 'dir') {
            result += `${indent}📁 ${item.name}/\n`;
            result += await walk(`${dirPath}/${item.name}`, currentDepth + 1);
          } else {
            result += `${indent}📄 ${item.name}\n`;
          }
        }
        return result;
      }

      let output = `🌳 文件树: ${startPath}/ (深度 ${maxDepth})\n\n`;
      output += await walk(startPath, 0);
      return { content: [{ type: 'text', text: output }] };
    }
  );
}

// ─── MCP Streamable HTTP (stateless mode) ───
app.post('/mcp', auth, async (req, res) => {
  try {
    const server = new McpServer({ name: 'repo-mcp-server', version: '1.0.0' });
    registerTools(server);
    const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
    res.on('close', () => { transport.close(); server.close(); });
    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (e) {
    if (!res.headersSent) {
      res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: e.message } });
    }
  }
});

// ─── 其他 MCP 路由 ───
app.get('/mcp', (_req, res) => res.status(405).json({ error: 'Method not allowed · stateless mode' }));
app.delete('/mcp', (_req, res) => res.status(405).json({ error: 'Method not allowed · stateless mode' }));

// ─── 健康检查 ───
app.get('/health', (_req, res) => {
  res.json({
    status: 'ok',
    server: 'repo-mcp-server',
    version: '1.0.0',
    repo: `${REPO_OWNER}/${REPO_NAME}`,
    tools: 13,
    auth: MCP_SECRET ? 'Bearer token' : 'local only',
    uptime: process.uptime()
  });
});

// ─── 启动 ───
app.listen(PORT, () => {
  console.log(`\n╔══════════════════════════════════════════════╗`);
  console.log(`║  仓库执行手 repo-mcp-server v1.0           ║`);
  console.log(`║  端口: ${PORT}                               ║`);
  console.log(`║  给 Notion 人格体操作代码仓库的 MCP 工具    ║`);
  console.log(`║  13 个工具就绪                              ║`);
  console.log(`╚══════════════════════════════════════════════╝\n`);
  console.log(`目标仓库: ${REPO_OWNER}/${REPO_NAME}`);
  console.log(`Gitea: ${GITEA_URL}`);
  console.log(`健康: http://localhost:${PORT}/health`);
  console.log(`MCP:  POST http://localhost:${PORT}/mcp`);
  console.log(`认证: ${MCP_SECRET ? '✅ Bearer Token' : '⚠️ 本地模式(仅127.0.0.1)'}`);
});
