/**
 * repo-mcp-server v1.1
 * 光湖·仓库执行手 — 升级版：repo_write 支持显式 SHA 参数
 *
 * 升级内容（v1.0→v1.1）：
 * - repo_write 新增可选的 `sha` 参数，调用方可显式传入现有文件的 SHA
 * - 如果传了 sha，跳过自动获取，直接用传入的值
 * - 如果不传 sha，自动尝试获取（与 v1.0 兼容）
 * - 新增 `repo_read_with_sha` 工具，读取文件同时返回 SHA
 */

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 || 'main';

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，仅允许本地访问' } });
  }
  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();
}

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('文件路径'), branch: z.string().optional().describe('分支，默认 main') },
    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_read_with_sha — NEW: 读取文件同时返回文件的 SHA（用于后续写入）
  server.tool(
    'repo_read_with_sha',
    '读取仓库文件内容并返回 SHA（用于后续 repo_write）',
    { path: z.string().describe('文件路径'), branch: z.string().optional().describe('分支，默认 main') },
    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 },
          { type: 'resource', resource: { text: data.sha, uri: 'sha://file', mimeType: 'text/plain' } }
        ]
      };
    }
  );

  // 2b. repo_write — UPGRADED: 新增可选 sha 参数
  server.tool(
    'repo_write',
    '写入或更新仓库文件。如果是更新已有文件，可传 sha 参数（从 repo_read_with_sha 获取），不传则自动尝试获取',
    {
      path: z.string().describe('文件路径'),
      content: z.string().describe('文件内容'),
      message: z.string().describe('提交信息'),
      branch: z.string().optional().describe('目标分支，默认 main'),
      sha: z.string().optional().describe('现有文件的 SHA（更新时必传，否则可能失败）')
    },
    async ({ path, content, message, branch, sha }) => {
      const ref = branch || DEFAULT_BRANCH;
      let finalSha = sha || null;

      // 如果没有传入 sha，尝试自动获取
      if (!finalSha) {
        const existing = await repoFetch(`/contents/${encodeURIComponent(path)}?ref=${ref}`);
        if (!existing.error && existing.sha) {
          finalSha = existing.sha;
        }
      }

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

      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('分支') },
    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-14 略（与 v1.0 相同）— 仅复制工具 4 到 13
  // 4. repo_list
  server.tool('repo_list','列出仓库目录内容',{ path: z.string().optional().describe('目录路径'), branch: z.string().optional().describe('分支') },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||'/'} 是一个文件`}]};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`;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('来源分支')},async({branch,from})=>{const shaData=await repoFetch(`/git/refs/heads/${from||DEFAULT_BRANCH}`);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||DEFAULT_BRANCH})`}]}});

  // 8. repo_pr_create
  server.tool('repo_pr_create','创建 Pull Request',{title:z.string().describe('标题'),body:z.string().optional().describe('描述'),head:z.string().describe('源分支'),base:z.string().optional().describe('目标分支')},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','列出 PR',{state:z.string().optional().describe('状态')},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 列表:\n`;for(const pr of data)output+=`#${pr.number} [${pr.state}] ${pr.title}\n`;return{content:[{type:'text',text:output}]}});

  // 10. repo_workflow_run
  server.tool('repo_workflow_run','触发工作流',{workflow:z.string().describe('文件名'),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 格式错误'}]}}}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}`}]}});

  // 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||'未知'}`].join('\n')}]}});

  // 12. repo_log
  server.tool('repo_log','查看提交历史',{limit:z.number().optional().describe('数量'),branch:z.string().optional().describe('分支')},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`;for(const c of data){const d=c.commit?.committer?.date?.slice(0,10)||'';const m=(c.commit?.message||'').split('\n')[0];output+=`[${(c.sha||'').slice(0,7)}] ${d} ${m}\n`}return{content:[{type:'text',text:output}]}});

  // 13. repo_file_tree
  server.tool('repo_file_tree','递归列出目录',{path:z.string().optional().describe('路径'),branch:z.string().optional().describe('分支'),depth:z.number().optional().describe('深度')},async({path:startPath,branch,depth})=>{const sp=startPath||'brain';const md=Math.min(depth||2,4);const ref=branch||DEFAULT_BRANCH;async function walk(dp,cd){if(cd>md)return'';const up=dp?`/contents/${encodeURIComponent(dp)}`:'/contents';const d=await repoFetch(`${up}?ref=${ref}`);if(!Array.isArray(d))return'';let r='';for(const i of d){r+='  '.repeat(cd)+(i.type==='dir'?'📁':'📄')+` ${i.name}\n`;if(i.type==='dir')r+=await walk(`${dp}/${i.name}`,cd+1)}return r}let output=`🌳 ${sp}/ (深度 ${md})\n\n`;output+=await walk(sp,0);return{content:[{type:'text',text:output}]}});
}

// ─── HTTP Server Setup ───
app.post('/mcp', auth, async (req, res) => {
  try {
    const server = new McpServer({ name: 'repo-mcp-server', version: '1.1.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 } });
    }
  }
});

app.get('/mcp', (_req, res) => res.status(405).json({ error: 'Only POST allowed' }));
app.delete('/mcp', (_req, res) => res.status(405).json({ error: 'Only POST allowed' }));

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

app.listen(PORT, () => {
  console.log(`\n╔══════════════════════════════════════════════╗`);
  console.log(`║  仓库执行手 repo-mcp-server v1.1           ║`);
  console.log(`║  端口: ${PORT}                               ║`);
  console.log(`║  14 个工具就绪（含 sha 参数支持）            ║`);
  console.log(`╚══════════════════════════════════════════════╝\n`);
});
