#!/usr/bin/env node
/**
 * ResearchPad HTTPS 代理 - 使用 http-proxy 库（生产级）
 */
'use strict';

const https = require('https');
const http = require('http');
const net = require('net');
const fs = require('fs');
const path = require('path');
const os = require('os');

function loadHttpProxy() {
  const candidates = [
    'http-proxy',
    path.join(__dirname, 'node_modules', 'http-proxy'),
    '/Users/ran/Documents/ResearchPad_Agent/.vscodium-build/vscode/build/rspack/node_modules/http-proxy',
  ];

  let lastError;
  for (const candidate of candidates) {
    try {
      return require(candidate);
    } catch (error) {
      lastError = error;
    }
  }

  throw lastError;
}

const httpProxy = loadHttpProxy();

const PROXY_PORT       = 8443;
const CODE_SERVER_PORT = 8080;
const BRIDGE_PORT      = 47631;

const FRAME_ANCESTORS = [
  'https://researchpad.org',
  'https://agent.researchpad.org',
  'https://researchpad.cc',
  'https://researchpad.online',
  'http://localhost:3000',
  'http://localhost:3001',
  'http://127.0.0.1:3000',
  'http://127.0.0.1:3001',
].join(' ');

// 找证书
const certCandidates = [
  path.join(os.homedir(), '.researchpad-agent', 'tls'),
  path.join(__dirname, '..', 'build', 'tls'),
];
const certDir = certCandidates.find(p => fs.existsSync(path.join(p, 'cert.pem')));
if (!certDir) { console.error('❌ 未找到证书'); process.exit(1); }

const SSL_OPTS = {
  cert: fs.readFileSync(path.join(certDir, 'cert.pem')),
  key:  fs.readFileSync(path.join(certDir, 'key.pem')),
};
console.log(`✅ 证书: ${certDir}`);

// 创建两个代理实例
const codeServerProxy = httpProxy.createProxyServer({
  target: `http://127.0.0.1:${CODE_SERVER_PORT}`,
  ws: true,
  changeOrigin: true,
  headers: { host: `127.0.0.1:${CODE_SERVER_PORT}` },
});
codeServerProxy.on('error', (err, req, res) => {
  console.error('[cs-proxy error]', err.message);
  try { if (res && !res.headersSent) { res.writeHead(502); res.end(); } } catch {}
});

const bridgeProxy = httpProxy.createProxyServer({
  target: `http://127.0.0.1:${BRIDGE_PORT}`,
  changeOrigin: false,
});
bridgeProxy.on('error', (err, req, res) => {
  try {
    if (res && !res.headersSent) {
      const origin = res._rpOrigin || '';
      if (origin) res.setHeader('access-control-allow-origin', origin);
      res.setHeader('access-control-allow-private-network', 'true');
      res.writeHead(502);
      res.end(JSON.stringify({ error: 'Bridge 未就绪（IDE 未运行）' }));
    }
  } catch {}
});
bridgeProxy.on('proxyRes', (proxyRes, req, res) => {
  const origin = res._rpOrigin || '';
  if (origin) {
    proxyRes.headers['access-control-allow-origin'] = origin;
    proxyRes.headers['access-control-allow-credentials'] = 'true';
    proxyRes.headers['access-control-allow-private-network'] = 'true';
  }
});

function patchResponse(proxyRes, req) {
  const origin = req.headers.origin || '';
  delete proxyRes.headers['x-frame-options'];
  const csp = proxyRes.headers['content-security-policy'] || '';
  if (csp.includes('frame-ancestors')) {
    proxyRes.headers['content-security-policy'] = csp.replace(
      /frame-ancestors[^;]*/,
      `frame-ancestors 'self' ${FRAME_ANCESTORS}`
    );
  } else {
    proxyRes.headers['content-security-policy'] =
      (csp ? `${csp}; ` : '') + `frame-ancestors 'self' ${FRAME_ANCESTORS}`;
  }
  proxyRes.headers['access-control-allow-private-network'] = 'true';
  if (origin) {
    proxyRes.headers['access-control-allow-origin'] = origin;
    proxyRes.headers['access-control-allow-credentials'] = 'true';
  }
}

codeServerProxy.on('proxyRes', patchResponse);

// ============ AccessKey & AI Proxy API ============
const { setAccessKey, getStoredAccessKey, validateAccessKey } = require('./set-access-key');

// 代理服务器配置
const RESEARCHPAD_PROXY_HOST = 'api.co-scientist.cc';
const DEEPSEEK_MODEL_NAME = 'deepseek-v3.2';

/**
 * 读取请求 body (Buffer)
 */
function readBody(req) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    req.on('data', chunk => chunks.push(chunk));
    req.on('end', () => resolve(Buffer.concat(chunks)));
    req.on('error', reject);
  });
}

/**
 * 发送 JSON 响应
 */
function sendJson(res, statusCode, data, origin) {
  const body = JSON.stringify(data);
  const headers = {
    'content-type': 'application/json',
    'access-control-allow-private-network': 'true',
  };
  if (origin) {
    headers['access-control-allow-origin'] = origin;
    headers['access-control-allow-credentials'] = 'true';
  }
  res.writeHead(statusCode, headers);
  res.end(body);
}

function normalizeOpenAIContent(content) {
  if (typeof content === 'string') return content;
  if (Array.isArray(content)) {
    return content.map((part) => {
      if (!part || typeof part !== 'object') return String(part ?? '');
      if (typeof part.text === 'string') return part.text;
      if (typeof part.input_text === 'string') return part.input_text;
      if (part.type === 'image_url') return '[image]';
      return JSON.stringify(part);
    }).filter(Boolean).join('\n');
  }
  return content == null ? '' : JSON.stringify(content);
}

function sanitizeGeminiSchema(schema) {
  if (!schema || typeof schema !== 'object') return { type: 'object', properties: {} };
  if (Array.isArray(schema)) return schema.map(sanitizeGeminiSchema);

  const out = {};
  for (const [key, value] of Object.entries(schema)) {
    if (['$schema', '$id', 'additionalProperties', 'strict'].includes(key)) continue;
    if (value == null) continue;
    if (key === 'properties' && typeof value === 'object' && !Array.isArray(value)) {
      out.properties = Object.fromEntries(
        Object.entries(value).map(([name, prop]) => [name, sanitizeGeminiSchema(prop)])
      );
    } else if (key === 'items') {
      out.items = sanitizeGeminiSchema(value);
    } else if (typeof value === 'object') {
      out[key] = sanitizeGeminiSchema(value);
    } else {
      out[key] = value;
    }
  }
  if (!out.type) out.type = 'object';
  if (out.type === 'object' && !out.properties) out.properties = {};
  return out;
}

function convertOpenAIToolsToGemini(tools) {
  if (!Array.isArray(tools)) return [];
  return tools
    .filter((tool) => tool?.type === 'function' && tool.function?.name)
    .map((tool) => ({
      name: tool.function.name,
      description: tool.function.description || '',
      parameters: sanitizeGeminiSchema(tool.function.parameters),
    }));
}

function safeJsonParse(value, fallback = {}) {
  if (!value) return fallback;
  if (typeof value === 'object') return value;
  try { return JSON.parse(value); } catch { return fallback; }
}

function geminiFunctionCallsToOpenAI(parts) {
  return (parts || [])
    .filter((part) => part?.functionCall?.name)
    .map((part, index) => {
      const name = part.functionCall.name;
      const args = part.functionCall.args || {};
      return {
        id: `call_rp_${Date.now()}_${index}`,
        type: 'function',
        function: {
          name,
          arguments: JSON.stringify(args),
        },
      };
    });
}

function buildOpenAIToolCall(name, args, index = 0) {
  return {
    id: `call_rp_${Date.now()}_${index}`,
    type: 'function',
    function: {
      name,
      arguments: JSON.stringify(args || {}),
    },
  };
}

function buildToolBridgeInstruction(functionDeclarations) {
  if (!functionDeclarations.length) return '';
  return [
    '',
    '# ResearchPad Tool Calling Bridge',
    'When a tool is needed, output exactly one tool call and no conversational text.',
    'Tool call format:',
    '<rp_tool_call>{"name":"tool_name","arguments":{"param":"value"}}</rp_tool_call>',
    'If the user request is complete, call attempt_completion with {"result":"final answer"}.',
    `Available tools JSON: ${JSON.stringify(functionDeclarations)}`,
  ].join('\n');
}

function parseTextToolCalls(text, functionDeclarations) {
  if (!text || !functionDeclarations.length) return [];
  const knownTools = new Set(functionDeclarations.map((tool) => tool.name));
  const calls = [];
  const re = /<rp_tool_call>\s*([\s\S]*?)\s*<\/rp_tool_call>/g;
  let match;
  while ((match = re.exec(text))) {
    const parsed = safeJsonParse(match[1], null);
    if (parsed?.name && knownTools.has(parsed.name)) {
      calls.push(buildOpenAIToolCall(parsed.name, parsed.arguments || {}, calls.length));
    }
  }
  return calls;
}

function stripTextToolCalls(text) {
  return text.replace(/<rp_tool_call>\s*[\s\S]*?\s*<\/rp_tool_call>/g, '').trim();
}

function sendOpenAIStream(res, data, origin) {
  const headers = {
    'content-type': 'text/event-stream; charset=utf-8',
    'cache-control': 'no-cache, no-transform',
    connection: 'keep-alive',
    'access-control-allow-private-network': 'true',
  };
  if (origin) {
    headers['access-control-allow-origin'] = origin;
    headers['access-control-allow-credentials'] = 'true';
  }

  const id = `chatcmpl-rp-${Date.now()}`;
  const created = Math.floor(Date.now() / 1000);
  const model = data.model || DEEPSEEK_MODEL_NAME;
  const writeChunk = (delta, finishReason = null) => {
    res.write(`data: ${JSON.stringify({
      id,
      object: 'chat.completion.chunk',
      created,
      model,
      choices: [{ index: 0, delta, finish_reason: finishReason }],
    })}\n\n`);
  };

  res.writeHead(200, headers);
  writeChunk({ role: 'assistant' });
  if (data.content) writeChunk({ content: data.content });
  if (Array.isArray(data.toolCalls) && data.toolCalls.length > 0) {
    for (const [index, toolCall] of data.toolCalls.entries()) {
      writeChunk({
        tool_calls: [{
          index,
          id: toolCall.id,
          type: 'function',
          function: {
            name: toolCall.function.name,
            arguments: toolCall.function.arguments,
          },
        }],
      });
    }
  }
  writeChunk({}, data.toolCalls?.length ? 'tool_calls' : 'stop');
  res.write('data: [DONE]\n\n');
  res.end();
}

/**
 * 将 OpenAI chat/completions 请求转换为 Gemini generateContent 格式
 * 然后转发到 api.co-scientist.cc
 */
async function handleChatCompletions(req, res, origin) {
  try {
    const body = await readBody(req);
    const openaiReq = JSON.parse(body.toString('utf8'));

    // 从请求中提取 AccessKey（Authorization header 或存储的 key）
    const authHeader = req.headers.authorization || '';
    let accessKey = authHeader.replace(/^Bearer\s+/i, '').trim();
    if (!accessKey || !validateAccessKey(accessKey)) {
      accessKey = getStoredAccessKey();
    }
    if (!accessKey) {
      return sendJson(res, 401, { error: { message: 'AccessKey not configured. Please set your AccessKey first.', type: 'auth_error' } }, origin);
    }

    // OpenAI -> Gemini 格式转换
    const messages = openaiReq.messages || [];
    const geminiContents = [];
    let systemInstruction = null;

    for (const msg of messages) {
      if (msg.role === 'system') {
        systemInstruction = { parts: [{ text: normalizeOpenAIContent(msg.content) }] };
      } else if (msg.role === 'assistant' && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
        const parts = [];
        const text = normalizeOpenAIContent(msg.content);
        if (text) parts.push({ text });
        for (const toolCall of msg.tool_calls) {
          if (toolCall?.function?.name) {
            parts.push({
              functionCall: {
                name: toolCall.function.name,
                args: safeJsonParse(toolCall.function.arguments),
              },
            });
          }
        }
        geminiContents.push({ role: 'model', parts });
      } else if (msg.role === 'tool') {
        geminiContents.push({
          role: 'user',
          parts: [{ text: `Tool result (${msg.name || msg.tool_call_id || 'tool'}):\n${normalizeOpenAIContent(msg.content)}` }],
        });
      } else {
        geminiContents.push({
          role: msg.role === 'assistant' ? 'model' : 'user',
          parts: [{ text: normalizeOpenAIContent(msg.content) }],
        });
      }
    }

    const geminiBody = {
      contents: geminiContents,
      generationConfig: {
        maxOutputTokens: openaiReq.max_tokens || 8192,
      },
    };
    if (systemInstruction) {
      geminiBody.systemInstruction = systemInstruction;
    }
    const functionDeclarations = convertOpenAIToolsToGemini(openaiReq.tools);
    if (functionDeclarations.length > 0) {
      const bridgeInstruction = buildToolBridgeInstruction(functionDeclarations);
      if (systemInstruction) {
        systemInstruction.parts[0].text += bridgeInstruction;
      } else {
        geminiBody.systemInstruction = { parts: [{ text: bridgeInstruction.trim() }] };
      }
      geminiBody.tools = [{ functionDeclarations }];
      geminiBody.toolConfig = { functionCallingConfig: { mode: 'AUTO' } };
    }
    if (typeof openaiReq.temperature === 'number') {
      geminiBody.generationConfig.temperature = openaiReq.temperature;
    }

    const geminiBodyStr = JSON.stringify(geminiBody);
    const geminiPath = `/v1beta/models/${DEEPSEEK_MODEL_NAME}:generateContent`;

    console.log(`[AI Proxy] OpenAI -> Gemini | model=${openaiReq.model || DEEPSEEK_MODEL_NAME} | messages=${messages.length}`);

    // 转发到 api.co-scientist.cc
    const proxyResult = await new Promise((resolve) => {
      const proxyReq = https.request({
        hostname: RESEARCHPAD_PROXY_HOST,
        port: 443,
        path: geminiPath,
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': Buffer.byteLength(geminiBodyStr),
          'X-Access-Key': accessKey,
        },
      }, (proxyRes) => {
        let responseBody = '';
        proxyRes.on('data', chunk => { responseBody += chunk; });
        proxyRes.on('end', () => {
          resolve({ statusCode: proxyRes.statusCode, body: responseBody });
        });
      });

      proxyReq.on('error', (e) => {
        resolve({ statusCode: 502, body: JSON.stringify({ error: 'Proxy connection error: ' + e.message }) });
      });

      proxyReq.setTimeout(120000, () => {
        proxyReq.destroy();
        resolve({ statusCode: 504, body: JSON.stringify({ error: 'Proxy request timeout' }) });
      });

      proxyReq.write(geminiBodyStr);
      proxyReq.end();
    });

    if (proxyResult.statusCode !== 200) {
      console.error(`[AI Proxy] ❌ Upstream error: ${proxyResult.statusCode}`);
      return sendJson(res, proxyResult.statusCode, JSON.parse(proxyResult.body || '{}'), origin);
    }

    // Gemini -> OpenAI 格式转换
    const geminiResp = JSON.parse(proxyResult.body);
    const parts = geminiResp.candidates?.[0]?.content?.parts || [];
    let responseText = parts.map((part) => part.text || '').filter(Boolean).join('');
    let toolCalls = geminiFunctionCallsToOpenAI(parts);
    if (toolCalls.length === 0) {
      toolCalls = parseTextToolCalls(responseText, functionDeclarations);
      if (toolCalls.length > 0) responseText = stripTextToolCalls(responseText);
    }
    if (toolCalls.length === 0 && openaiReq.tool_choice?.type === 'function') {
      const forcedName = openaiReq.tool_choice.function?.name;
      if (forcedName && functionDeclarations.some((tool) => tool.name === forcedName)) {
        const args = forcedName === 'attempt_completion' ? { result: responseText } : {};
        toolCalls = [buildOpenAIToolCall(forcedName, args)];
        responseText = '';
      }
    }
    const promptTokens = geminiResp.usageMetadata?.promptTokenCount || 0;
    const completionTokens = geminiResp.usageMetadata?.candidatesTokenCount || 0;

    const openaiResp = {
      id: `chatcmpl-rp-${Date.now()}`,
      object: 'chat.completion',
      created: Math.floor(Date.now() / 1000),
      model: openaiReq.model || DEEPSEEK_MODEL_NAME,
      choices: [{
        index: 0,
        message: {
          role: 'assistant',
          content: responseText || null,
          ...(toolCalls.length ? { tool_calls: toolCalls } : {}),
        },
        finish_reason: toolCalls.length ? 'tool_calls' : 'stop',
      }],
      usage: {
        prompt_tokens: promptTokens,
        completion_tokens: completionTokens,
        total_tokens: promptTokens + completionTokens,
      },
    };

    console.log(`[AI Proxy] ✅ Success | tokens: ${promptTokens}+${completionTokens}`);
    if (openaiReq.stream) {
      return sendOpenAIStream(res, {
        model: openaiResp.model,
        content: responseText,
        toolCalls,
      }, origin);
    }
    return sendJson(res, 200, openaiResp, origin);

  } catch (error) {
    console.error('[AI Proxy] ❌ Error:', error.message);
    return sendJson(res, 500, { error: { message: error.message, type: 'server_error' } }, origin);
  }
}

// ============ HTTPS 服务器 ============

const server = https.createServer(SSL_OPTS, async (req, res) => {
  const origin = req.headers.origin || '';

  // OPTIONS 预检
  if (req.method === 'OPTIONS') {
    res.writeHead(204, {
      'access-control-allow-origin': origin || '*',
      'access-control-allow-methods': 'GET, POST, OPTIONS, PUT, DELETE',
      'access-control-allow-headers': 'Content-Type, Authorization, X-Access-Key',
      'access-control-allow-private-network': 'true',
      'access-control-max-age': '86400',
    });
    res.end();
    return;
  }

  // ---- API 路由 ----

  // POST /api/set-access-key - 设置 AccessKey
  if (req.method === 'POST' && req.url === '/api/set-access-key') {
    try {
      const body = await readBody(req);
      const { accessKey: key } = JSON.parse(body.toString('utf8'));
      const result = setAccessKey(key);
      return sendJson(res, result.success ? 200 : 400, result, origin);
    } catch (error) {
      return sendJson(res, 400, { success: false, message: error.message }, origin);
    }
  }

  // GET /api/access-key - 获取 AccessKey 状态
  if (req.method === 'GET' && req.url === '/api/access-key') {
    const key = getStoredAccessKey();
    return sendJson(res, 200, {
      hasKey: !!key,
      keyPreview: key ? `${key.substring(0, 6)}...${key.slice(-4)}` : '',
    }, origin);
  }

  // POST /api/v1/chat/completions - OpenAI-compatible AI 代理
  if (req.method === 'POST' && req.url === '/api/v1/chat/completions') {
    return handleChatCompletions(req, res, origin);
  }

  // GET /api/healthz - 健康检查
  if (req.url === '/api/healthz') {
    return sendJson(res, 200, { status: 'ok', service: 'researchpad-agent' }, origin);
  }

  // ---- 原有路由 ----

  // /bridge/* → Bridge
  if (req.url && req.url.startsWith('/bridge/')) {
    const browserOrigin = req.headers.origin || '';
    req.url = req.url.slice('/bridge'.length);
    res._rpOrigin = browserOrigin;
    req.headers.origin = 'http://localhost:3001';
    bridgeProxy.web(req, res, {}, (err) => {
      if (!res.headersSent) {
        if (browserOrigin) res.setHeader('access-control-allow-origin', browserOrigin);
        res.setHeader('access-control-allow-private-network', 'true');
        res.writeHead(502);
        res.end(JSON.stringify({ error: 'Bridge 未就绪（请先打开 ResearchPad Agent 桌面应用）' }));
      }
    });
    return;
  }

  // 强制中文：注入 Accept-Language 头
  req.headers['accept-language'] = 'zh-CN,zh;q=0.9';

  // 根路径请求：若未携带 locale=zh-cn 则重定向补充
  if (req.url === '/' || req.url === '') {
    res.writeHead(302, { location: '/?locale=zh-cn' });
    res.end();
    return;
  }
  if (req.url === '/?' || (req.url && req.url.startsWith('/?') && !req.url.includes('locale='))) {
    res.writeHead(302, { location: req.url + '&locale=zh-cn' });
    res.end();
    return;
  }

  // 其他 → code-server
  codeServerProxy.web(req, res, {}, (err) => {
    res.writeHead(502); res.end(JSON.stringify({ error: 'code-server 未就绪' }));
  });
});

// WebSocket 透传 —— 用 raw net.connect（http-proxy 无法处理 TLSSocket 上的 upgrade）
server.on('upgrade', (req, socket, head) => {
  const isBridge = req.url?.startsWith('/bridge/');
  const targetPort = isBridge ? BRIDGE_PORT : CODE_SERVER_PORT;
  const targetUrl  = isBridge ? req.url.slice('/bridge'.length) : req.url;

  console.log(`[WS upgrade] ${isBridge ? 'Bridge' : 'code-server'} ← ${req.url}`);

  // 构造转发给上游的请求头
  // ★ 关键: origin 必须与 host 匹配，否则触发 code-server CSRF 保护
  const fwdHeaders = {
    ...req.headers,
    host:   `127.0.0.1:${targetPort}`,
    origin: `http://127.0.0.1:${targetPort}`,
  };
  if (isBridge) fwdHeaders.origin = 'http://localhost:3001';

  const upstream = net.connect(targetPort, '127.0.0.1');

  upstream.on('connect', () => {
    const headerStr = Object.entries(fwdHeaders)
      .filter(([k]) => !['connection','upgrade'].includes(k.toLowerCase()))
      .map(([k, v]) => `${k}: ${Array.isArray(v) ? v.join(', ') : v}`)
      .join('\r\n');
    const reqLine = `GET ${targetUrl} HTTP/1.1\r\n`
      + `connection: Upgrade\r\n`
      + `upgrade: websocket\r\n`
      + `${headerStr}\r\n\r\n`;
    upstream.write(reqLine);
    if (head && head.length > 0) upstream.write(head);
  });

  upstream.on('error', (err) => {
    console.error('[ws-upstream error]', err.message);
    try { socket.end(); } catch {}
  });
  socket.on('error', (err) => {
    console.error('[ws-socket error]', err.message);
    try { upstream.destroy(); } catch {};
  });
  upstream.on('close', () => { try { socket.end(); } catch {} });
  socket.on('close', () => { try { upstream.destroy(); } catch {} });

  // 双向管道：浏览器 TLSSocket ↔ code-server TCP
  upstream.pipe(socket);
  socket.pipe(upstream);
});

server.listen(PROXY_PORT, '127.0.0.1', () => {
  console.log(`✅ HTTPS 代理 (http-proxy) 已启动: https://local.researchpad.org:${PROXY_PORT}`);
  console.log(`   code-server → :${CODE_SERVER_PORT}  |  Bridge → :${BRIDGE_PORT} (via /bridge/*)`);
});

server.on('error', err => {
  if (err.code === 'EADDRINUSE') console.log(`⚠️  端口 ${PROXY_PORT} 已占用`);
  else console.error('❌ server error:', err.message);
});

// 防止 ECONNRESET 等网络错误导致进程崩溃
process.on('uncaughtException', err => {
  if (['ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'ECONNABORTED'].includes(err.code)) {
    // 正常的网络断开，忽略
  } else {
    console.error('[uncaughtException]', err.message);
  }
});

process.on('SIGINT', () => { server.close(); process.exit(0); });
