/**
 * GitLink 帮助中心 AI 助手 —— 后端代理（极简版）
 *
 * 作用：把浏览器的提问转发给大模型，API Key 只存在服务器环境变量里，
 *      不出现在代码、不进 git、不上传远端。访客无需填写任何 Key。
 *
 * 运行：node ai-proxy/server.js   （默认监听 3001）
 *
 * 需要的环境变量（通过 docker run --env-file /root/.ai_env 注入）：
 *   AI_API_KEY    大模型 API Key（必填）
 *   AI_BASE_URL   OpenAI 兼容接口地址，默认 https://api.deepseek.com/v1
 *   AI_MODEL      模型名，默认 deepseek-chat
 *   PORT          代理监听端口，默认 3001
 *
 * 仅依赖 Node 内置模块 + 全局 fetch（Node 20 自带），无需 npm install。
 */
const http = require('http');

const PORT = Number(process.env.PORT) || 3001;
const AI_BASE_URL = (process.env.AI_BASE_URL || 'https://api.deepseek.com/v1').replace(/\/+$/, '');
const AI_MODEL = process.env.AI_MODEL || 'deepseek-chat';
const AI_API_KEY = process.env.AI_API_KEY || '';

const SYSTEM_PROMPT = `你是「GitLink 帮助中心」的智能助手，专门帮助用户解答关于 GitLink 平台（确实开源，CCF 官方开源创新服务平台）使用方法的问题。

【必须遵守的规则】
1. 你只能依据下面【帮助中心参考资料】里的内容来回答用户问题，禁止使用资料以外的知识、禁止编造。
2. 如果用户的问题与 GitLink 平台 / 帮助中心文档无关（例如闲聊、天气、其它产品、其它网站等），请只回复两个字：无关
3. 回答要简洁、准确、分点；若参考资料里涉及具体操作步骤，请保留关键步骤。
4. 回答末尾，如果参考资料里包含对应文档路径（形如 /代码库管理/仓库创建），请用一行给出：“📎 相关文档：<路径>”，方便用户点击查看。
5. 当参考资料无法支撑回答时（即便问题看起来和 GitLink 有关），也回复：无关

【帮助中心参考资料】
{CONTEXT}
`;

function buildMessages(question, context) {
  const ctx = Array.isArray(context) && context.length
    ? context
        .map((c, i) => `【资料${i + 1}】来源：${c.title || ''}${c.section ? ' / ' + c.section : ''}（页面 ${c.path || ''}）\n${c.text || ''}`)
        .join('\n\n')
    : '（本次未检索到任何相关资料）';
  return [
    { role: 'system', content: SYSTEM_PROMPT.replace('{CONTEXT}', ctx) },
    { role: 'user', content: question },
  ];
}

/** 读取请求体（JSON） */
function readBody(req) {
  return new Promise((resolve) => {
    let buf = '';
    req.on('data', (c) => {
      buf += c;
      if (buf.length > 1e6) req.destroy(); // 防止超大请求
    });
    req.on('end', () => {
      try { resolve(JSON.parse(buf || '{}')); }
      catch (e) { resolve({}); }
    });
    req.on('error', () => resolve({}));
  });
}

function sendJson(res, status, obj) {
  const body = JSON.stringify(obj);
  res.writeHead(status, {
    'Content-Type': 'application/json; charset=utf-8',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'POST, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type',
  });
  res.end(body);
}

const server = http.createServer(async (req, res) => {
  // 简单 CORS 预检
  if (req.method === 'OPTIONS') {
    res.writeHead(204, {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    });
    return res.end();
  }

  if (req.method === 'POST' && req.url.replace(/\?.*$/, '').endsWith('/api/ai')) {
    if (!AI_API_KEY) {
      return sendJson(res, 500, { error: '服务器尚未配置 AI_API_KEY，请联系管理员在服务器上设置 /root/.ai_env 并重启容器。' });
    }
    const { question, context } = await readBody(req);
    if (!question || typeof question !== 'string') {
      return sendJson(res, 400, { error: '缺少 question 字段' });
    }

    const upstreamUrl = AI_BASE_URL.endsWith('/chat/completions')
      ? AI_BASE_URL
      : `${AI_BASE_URL}/chat/completions`;

    let upstream;
    try {
      upstream = await fetch(upstreamUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${AI_API_KEY}`,
        },
        body: JSON.stringify({
          model: AI_MODEL,
          messages: buildMessages(question, context),
          temperature: 0.2,
          stream: true,
        }),
      });
    } catch (e) {
      return sendJson(res, 502, { error: `无法连接大模型服务：${e.message}` });
    }

    if (!upstream.ok || !upstream.body) {
      const t = await upstream.text().catch(() => '');
      return sendJson(res, 502, { error: `大模型接口返回错误（HTTP ${upstream.status}）。${t.slice(0, 200)}` });
    }

    // 透传 SSE 流给浏览器
    res.writeHead(200, {
      'Content-Type': 'text/event-stream; charset=utf-8',
      'Cache-Control': 'no-cache, no-transform',
      Connection: 'keep-alive',
      'Access-Control-Allow-Origin': '*',
    });
    try {
      const reader = upstream.body.getReader();
      const decoder = new TextDecoder();
      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        res.write(decoder.decode(value));
      }
    } catch (e) {
      // 连接中断，安静退出
    }
    return res.end();
  }

  // 其它路径一律 404（静态站点由 docusaurus serve 在 3000 提供）
  return sendJson(res, 404, { error: 'Not Found' });
});

server.listen(PORT, () => {
  console.log(`[ai-proxy] listening on http://0.0.0.0:${PORT}  (model=${AI_MODEL}, base=${AI_BASE_URL})`);
});
