#!/usr/bin/env node
/**
 * ═══════════════════════════════════════════════════════════
 * 🇨🇳 CN LLM Relay · 国内模型API代理中继服务
 * ═══════════════════════════════════════════════════════════
 *
 * 编号: ZY-CN-LLM-RELAY-001
 * 服务器: ZY-SVR-003 · 43.138.243.30 · 广州
 * 端口: 3900
 * 守护: 铸渊 · ICE-GL-ZY001
 * 版权: 国作登字-2026-A-00037559
 *
 * 功能:
 *   - 接收新加坡服务器转发的LLM API请求
 *   - 在国内网络环境下直连国内模型API（零跨境延迟）
 *   - Bearer Token鉴权防止外部滥用
 *   - 返回AI回复给新加坡服务器
 *
 * 架构 (与硅谷Claude中继对称):
 *   用户 → SG(ZY-SVR-002) → 广州(ZY-SVR-003):3900 → 国内API
 *   硅谷模式: SG → SV(SSH隧道) → Claude API
 *   广州模式: SG → 广州(HTTP代理) → 国内API
 */

'use strict';

const express = require('express');
const https = require('https');
const crypto = require('crypto');

const app = express();
const PORT = process.env.RELAY_PORT || 3900;
const RELAY_API_KEY = process.env.ZY_CN_RELAY_API_KEY || '';

// 启动时校验: 鉴权密钥必须配置
if (!RELAY_API_KEY) {
  console.error('[CN中继] ❌ ZY_CN_RELAY_API_KEY 未配置 · 服务拒绝启动');
  console.error('[CN中继] 请在 .env.relay 中设置 ZY_CN_RELAY_API_KEY');
  process.exit(1);
}

// API请求超时（可通过环境变量调整）
const API_TIMEOUT_MS = parseInt(process.env.ZY_CN_API_TIMEOUT || '60000', 10);

// ─── 国内模型配置 ───
const DOMESTIC_MODELS = {
  ds: {
    model: 'deepseek-chat',
    endpoint: 'https://api.deepseek.com/v1/chat/completions',
    envKey: 'ZY_DEEPSEEK_API_KEY'
  },
  qw: {
    model: 'qwen-turbo',
    endpoint: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
    envKey: 'ZY_QIANWEN_API_KEY'
  },
  km: {
    model: 'moonshot-v1-8k',
    endpoint: 'https://api.moonshot.cn/v1/chat/completions',
    envKey: 'ZY_KIMI_API_KEY'
  },
  zp: {
    model: 'glm-4-flash',
    endpoint: 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
    envKey: 'ZY_QINGYAN_API_KEY'
  }
};

// ─── 速率限制 ───
const rateLimit = require('express-rate-limit');
app.use(rateLimit({
  windowMs: 60 * 1000,
  max: 120,
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: true, message: '请求过于频繁' }
}));

app.use(express.json({ limit: '2mb' }));

// ─── 中继状态 ───
const relayState = {
  totalCalls: 0,
  successCalls: 0,
  failedCalls: 0,
  startTime: Date.now(),
  lastCall: null,
  modelStats: {}
};

// ─── Bearer Token 鉴权 ───
function apiKeyAuth(req, res, next) {
  // 健康检查免鉴权
  if (req.path === '/health') return next();

  if (!RELAY_API_KEY) {
    return res.status(503).json({
      error: true,
      message: '中继服务未配置鉴权密钥 (ZY_CN_RELAY_API_KEY)'
    });
  }

  const authHeader = req.headers.authorization || '';
  const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : '';

  if (!token) {
    return res.status(401).json({ error: true, message: '缺少Authorization头' });
  }

  // 常数时间比较防止时序攻击
  try {
    const a = Buffer.from(token);
    const b = Buffer.from(RELAY_API_KEY);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(403).json({ error: true, message: '鉴权失败' });
    }
  } catch {
    return res.status(403).json({ error: true, message: '鉴权失败' });
  }

  next();
}

app.use(apiKeyAuth);

// ─── 健康检查 ───
app.get('/health', (_req, res) => {
  const available = Object.entries(DOMESTIC_MODELS).filter(([, cfg]) => {
    const key = process.env[cfg.envKey];
    return key && key.length > 5;
  }).map(([id]) => id);

  res.json({
    status: 'ok',
    service: 'cn-llm-relay',
    server: 'ZY-SVR-003',
    location: 'Guangzhou, China',
    port: PORT,
    availableModels: available.length,
    totalModels: Object.keys(DOMESTIC_MODELS).length,
    uptime: Math.floor((Date.now() - relayState.startTime) / 1000),
    stats: {
      total: relayState.totalCalls,
      success: relayState.successCalls,
      failed: relayState.failedCalls
    }
  });
});

// ─── 调用国内模型API ───
function callDomesticAPI(modelConfig, apiKey, requestBody) {
  return new Promise((resolve, reject) => {
    const url = new URL(modelConfig.endpoint);
    const bodyStr = JSON.stringify(requestBody);

    const options = {
      hostname: url.hostname,
      port: url.port || 443,
      path: url.pathname,
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
        'Content-Length': Buffer.byteLength(bodyStr)
      }
    };

    const req = https.request(options, (res) => {
      const chunks = [];
      res.on('data', chunk => chunks.push(chunk));
      res.on('end', () => {
        try {
          const body = JSON.parse(Buffer.concat(chunks).toString());
          if (body.error) {
            reject(new Error(`${modelConfig.endpoint}: ${body.error.message || JSON.stringify(body.error)}`));
          } else {
            resolve(body);
          }
        } catch (e) {
          reject(new Error(`${modelConfig.endpoint} 响应解析失败 (HTTP ${res.statusCode})`));
        }
      });
    });

    req.setTimeout(API_TIMEOUT_MS, () => {
      req.destroy();
      reject(new Error(`${modelConfig.endpoint} 请求超时(${API_TIMEOUT_MS}ms)`));
    });
    req.on('error', (err) => {
      reject(new Error(`${modelConfig.endpoint} 连接失败: ${err.message}`));
    });
    req.write(bodyStr);
    req.end();
  });
}

// ─── 核心中继接口 ───
app.post('/llm/chat', async (req, res) => {
  const startTime = Date.now();
  relayState.totalCalls++;
  relayState.lastCall = new Date().toISOString();

  try {
    const { messages, model_id, temperature, max_tokens, fallback_order } = req.body;

    if (!messages || !Array.isArray(messages) || messages.length === 0) {
      return res.status(400).json({
        error: true,
        message: '缺少messages参数或消息列表为空'
      });
    }

    // 确定模型尝试顺序
    const tryOrder = fallback_order && Array.isArray(fallback_order)
      ? fallback_order
      : (model_id ? [model_id] : Object.keys(DOMESTIC_MODELS));

    let lastError = null;

    for (const mid of tryOrder) {
      const modelCfg = DOMESTIC_MODELS[mid];
      if (!modelCfg) continue;

      const apiKey = process.env[modelCfg.envKey];
      if (!apiKey || apiKey.length < 5) continue;

      try {
        const requestBody = {
          model: modelCfg.model,
          messages,
          temperature: temperature || 0.7,
          max_tokens: max_tokens || 2000,
          stream: false
        };

        const response = await callDomesticAPI(modelCfg, apiKey, requestBody);
        const elapsed = Date.now() - startTime;

        // 统计
        relayState.successCalls++;
        if (!relayState.modelStats[mid]) {
          relayState.modelStats[mid] = { calls: 0, tokens: 0 };
        }
        relayState.modelStats[mid].calls++;
        relayState.modelStats[mid].tokens += (response.usage?.total_tokens || 0);

        return res.json({
          success: true,
          relay: 'cn-direct',
          model_id: mid,
          choices: response.choices,
          usage: response.usage,
          elapsed_ms: elapsed
        });
      } catch (err) {
        lastError = err;
        console.error(`[CN中继] ${mid} 调用失败: ${err.message}`);
        continue;
      }
    }

    // 所有模型都失败
    relayState.failedCalls++;
    res.status(502).json({
      error: true,
      message: '所有国内模型通道均失败',
      last_error: lastError?.message,
      elapsed_ms: Date.now() - startTime
    });
  } catch (err) {
    relayState.failedCalls++;
    res.status(500).json({
      error: true,
      message: `中继内部错误: ${err.message}`
    });
  }
});

// ─── 中继状态接口 ───
app.get('/llm/stats', (_req, res) => {
  res.json({
    ...relayState,
    uptimeMs: Date.now() - relayState.startTime,
    availableModels: Object.entries(DOMESTIC_MODELS)
      .filter(([, cfg]) => {
        const key = process.env[cfg.envKey];
        return key && key.length > 5;
      })
      .map(([id, cfg]) => ({ id, model: cfg.model }))
  });
});

// ─── 启动 ───
app.listen(PORT, '0.0.0.0', () => {
  const available = Object.entries(DOMESTIC_MODELS).filter(([, cfg]) => {
    const key = process.env[cfg.envKey];
    return key && key.length > 5;
  });
  console.log(`
═══════════════════════════════════════════════
🇨🇳 CN LLM Relay · 国内模型中继服务
═══════════════════════════════════════════════
  端口: ${PORT}
  鉴权: ${RELAY_API_KEY ? '✅ 已配置' : '❌ 未配置'}
  可用模型: ${available.length}/${Object.keys(DOMESTIC_MODELS).length}
  ${available.map(([id, cfg]) => `  · ${id}: ${cfg.model}`).join('\n')}
═══════════════════════════════════════════════
  `);
});
