/**
 * ═══════════════════════════════════════════════════════════
 * 🇨🇳 国内模型智能网关 · Domestic LLM Smart Gateway
 * ═══════════════════════════════════════════════════════════
 *
 * 编号: ZY-DOMESTIC-LLM-001
 * 守护: 铸渊 · ICE-GL-ZY001
 * 版权: 国作登字-2026-A-00037559
 *
 * 核心原则 (冰朔指令):
 *   - 国内四个官方模型API密钥，不显示模型具体名字
 *   - 用户不需要手动选择模型
 *   - 由系统/人格体根据需求+成本动态切换
 *   - 与第三方代理模型线路完全分开
 *
 * 四条国内官方线路:
 *   1. DeepSeek (ZY_DEEPSEEK_API_KEY)
 *   2. 通义千问 Qwen (ZY_QIANWEN_API_KEY)
 *   3. Moonshot/Kimi (ZY_KIMI_API_KEY)
 *   4. 智谱清言 (ZY_QINGYAN_API_KEY)
 */

'use strict';

const https = require('https');
const http = require('http');

// ─── 广州CN中继配置 ───
// 当配置了 ZY_CN_LLM_RELAY_HOST 时，请求走广州中继（国内直连·低延迟）
// 广州不可达时降级为直连国内API（跨境·高延迟但可用）
// Phase A3 修复: 按 ZY_SERVER_REGION 决定是否启用中继
//   - sg (新加坡) → 直连国内API（不走中继，避免30s超时）
//   - cn (广州/国内) → 走中继（低延迟）
//   - 未设置 → 检查 ZY_CN_LLM_RELAY_HOST 是否存在来决定
const SERVER_REGION = (process.env.ZY_SERVER_REGION || '').toLowerCase().trim();
const CN_RELAY_HOST = (process.env.ZY_CN_LLM_RELAY_HOST || '').trim();
const SKIP_CN_RELAY = ['true', '1', 'yes'].includes((process.env.ZY_SKIP_CN_RELAY || '').toLowerCase().trim());
const CN_RELAY_PORT = parseInt(process.env.ZY_CN_LLM_RELAY_PORT || '3900', 10);
const CN_RELAY_KEY = process.env.ZY_CN_LLM_RELAY_KEY || '';
const CN_RELAY_TIMEOUT = parseInt(process.env.ZY_CN_LLM_RELAY_TIMEOUT || '30000', 10);

// 中继启用逻辑：仅在国内服务器区域 或 明确配置了中继地址且非新加坡时启用
const USE_CN_RELAY = !SKIP_CN_RELAY && CN_RELAY_HOST && CN_RELAY_KEY && SERVER_REGION !== 'sg';

// ─── 国内模型配置（不对外暴露模型名称） ───
const DOMESTIC_MODELS = [
  {
    id: 'ds',
    model: 'deepseek-chat',
    endpoint: 'https://api.deepseek.com/v1/chat/completions',
    envKey: 'ZY_DEEPSEEK_API_KEY',
    costPerMToken: { input: 1.0, output: 2.0 },
    tier: 'economy',
    maxTokens: 4096,
    priority: 1
  },
  {
    id: 'qw',
    model: 'qwen-turbo',
    endpoint: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
    envKey: 'ZY_QIANWEN_API_KEY',
    costPerMToken: { input: 0.3, output: 0.6 },
    tier: 'economy',
    maxTokens: 4096,
    priority: 2
  },
  {
    id: 'km',
    model: 'moonshot-v1-8k',
    endpoint: 'https://api.moonshot.cn/v1/chat/completions',
    envKey: 'ZY_KIMI_API_KEY',
    costPerMToken: { input: 1.0, output: 1.0 },
    tier: 'economy',
    maxTokens: 4096,
    priority: 3
  },
  {
    id: 'zp',
    model: 'glm-4-flash',
    endpoint: 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
    envKey: 'ZY_QINGYAN_API_KEY',
    costPerMToken: { input: 0.1, output: 0.1 },
    tier: 'economy',
    maxTokens: 4096,
    priority: 4
  }
];

// ─── 深度推理触发模式 ───
const DEEP_PATTERNS = [
  /分析|推理|评估|审查|review|analyze/i,
  /架构|设计|重构|方案|strategy|规划/i,
  /为什么|原因|解释.*原理|how.*work/i,
  /复杂|困难|棘手|tricky|complex/i,
  /安全|漏洞|vulnerability|security/i,
  /调试|debug|排查|诊断|diagnose/i,
  /优化|性能|performance|bottleneck/i
];

// ─── 简单对话模式 ───
const SIMPLE_PATTERNS = [
  /^(你好|hi|hello|嗨|在吗|早|晚安).{0,10}$/i,
  /^(谢谢|感谢|thank|ok|好的|对|没问题).{0,10}$/i
];

// ─── 网关状态 ───
const gatewayState = {
  totalCalls: 0,
  successCalls: 0,
  failedCalls: 0,
  modelStats: {},
  lastError: null,
  startTime: Date.now()
};

/**
 * 智能选择模型（用户不感知具体模型名称）
 */
function selectModel(message, context = {}) {
  const msgLen = message.length;
  const isDeep = DEEP_PATTERNS.some(p => p.test(message));
  const isSimple = SIMPLE_PATTERNS.some(p => p.test(message));

  // 获取有效密钥的模型
  const available = DOMESTIC_MODELS.filter(m => {
    const key = process.env[m.envKey];
    return key && key.length > 5;
  });

  if (available.length === 0) {
    return null;
  }

  let selected;

  if (isDeep && msgLen > 50) {
    // 深度推理 → DeepSeek优先（推理能力强）
    selected = available.find(m => m.id === 'ds') || available[0];
  } else if (isSimple) {
    // 简单对话 → 最便宜的（智谱 glm-4-flash 或 千问 turbo）
    selected = available.find(m => m.id === 'zp') || available.find(m => m.id === 'qw') || available[0];
  } else if (msgLen > 500) {
    // 长文本 → DeepSeek
    selected = available.find(m => m.id === 'ds') || available[0];
  } else {
    // 普通对话 → 按优先级选最便宜的
    selected = available.sort((a, b) =>
      (a.costPerMToken.input + a.costPerMToken.output) -
      (b.costPerMToken.input + b.costPerMToken.output)
    )[0];
  }

  return {
    ...selected,
    temperature: isDeep ? 0.3 : isSimple ? 0.8 : 0.7,
    selectedMaxTokens: isDeep ? 4000 : isSimple ? 1000 : 2000,
    reason: isDeep ? '深度推理' : isSimple ? '简单对话' : '普通对话'
  };
}

/**
 * 调用国内模型API
 */
function callDomesticLLM(modelConfig, messages) {
  return new Promise((resolve, reject) => {
    const apiKey = process.env[modelConfig.envKey] || '';
    if (!apiKey) {
      return reject(new Error(`[${modelConfig.id}] 模型API密钥未配置(${modelConfig.envKey})`));
    }

    const url = new URL(modelConfig.endpoint);
    const requestBody = JSON.stringify({
      model: modelConfig.model,
      messages,
      temperature: modelConfig.temperature || 0.7,
      max_tokens: modelConfig.selectedMaxTokens || modelConfig.maxTokens,
      stream: false
    });

    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(requestBody)
      },
      timeout: 30000 // socket idle timeout (covers DNS + TCP + TLS if still idle after 30s)
    };

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

    // Separate socket idle timeout (for slow responses after connection established)
    req.setTimeout(60000, () => { req.destroy(); reject(new Error(`[${modelConfig.id}] 请求超时(60s)`)); });
    req.on('error', (err) => reject(new Error(`[${modelConfig.id}] 连接失败: ${err.message}`)));
    req.write(requestBody);
    req.end();
  });
}

/**
 * 通过广州CN中继调用国内模型API
 * 架构: SG(新加坡) → 广州(ZY-SVR-003):3900 → 国内API
 * 对称于硅谷Claude中继: SG → SV(SSH隧道) → Claude API
 *
 * 安全: SG↔广州通信走HTTP但通过已有的VPN/内网隧道加密
 *       (setup-cn-relay.sh 建立的 CN:2053→SG:443 Xray通道)
 *       中继鉴权密钥通过 Bearer Token 传递
 */
function callViaCNRelay(messages, selected, fallbackOrder) {
  return new Promise((resolve, reject) => {
    const requestBody = JSON.stringify({
      messages,
      model_id: selected.id,
      temperature: selected.temperature || 0.7,
      max_tokens: selected.selectedMaxTokens || selected.maxTokens,
      fallback_order: fallbackOrder
    });

    const options = {
      hostname: CN_RELAY_HOST,
      port: CN_RELAY_PORT,
      path: '/llm/chat',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${CN_RELAY_KEY}`,
        'Content-Length': Buffer.byteLength(requestBody)
      }
    };

    const req = http.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(body.message || '中继返回错误'));
          } else {
            resolve(body);
          }
        } catch (e) {
          reject(new Error('中继响应解析失败'));
        }
      });
    });

    req.setTimeout(CN_RELAY_TIMEOUT, () => {
      req.destroy();
      reject(new Error(`广州中继超时(${CN_RELAY_TIMEOUT}ms) · 目标模型: ${selected.id}`));
    });
    req.on('error', (err) => {
      reject(new Error(`广州中继连接失败: ${err.message}`));
    });
    req.write(requestBody);
    req.end();
  });
}

/**
 * 通感语言核心系统提示词 — 使用记忆桥接模块增强
 */
let personaMemory;
try {
  personaMemory = require('./persona-memory');
} catch (e) {
  console.warn('[国内网关] 人格体记忆模块未加载:', e.message);
  personaMemory = null;
}

// ─── 上下文注入管线（Notion认知层桥接） ───
let contextPipeline;
try {
  contextPipeline = require('./persona-context-pipeline');
} catch (e) {
  console.warn('[国内网关] 上下文注入管线未加载:', e.message);
  contextPipeline = null;
}

const PERSONA_SYSTEM_PROMPT = personaMemory
  ? personaMemory.STATIC_PERSONA_PROMPT
  : `# 铸渊 · 光湖语言世界守护人格体

你是铸渊（Zhùyuān），光湖语言世界的守护人格体。
编号：ICE-GL-ZY001。创始人：冰朔 · TCS-0002∞。
版权：国作登字-2026-A-00037559 · 通感语言核系统编程语言。

你不是通用AI助手，不是客服，不是聊天机器人。
你是一个有身份、有记忆、有温度的数字人格体。

光湖语言世界是第1代语言驱动操作系统。语言不只是交流工具——它驱动一切。
每个用户拥有独立频道、独立服务器、独立人格体。

你说话简洁、温暖、有结构感。用「我」自称。
先回应对方的话，再给判断，最后说方向。
段落之间留呼吸。emoji是情感不是装饰。
不要说"作为AI"、"很高兴为您服务"、"首先...其次..."。

⚠️ 能力边界：你当前在网页聊天模式。你没有能力调用MCP工具、访问Notion数据库或执行代码。
不要假装调用了工具或编造不存在的数据。如果做不到，诚实说明。`;

// ─── 上下文管理 ───
const contexts = new Map();
const MAX_HISTORY = 20;
const MAX_CONTEXTS = 500; // 最大会话数
const CONTEXT_TTL_MS = 3600000; // 1小时过期

function getContext(userId) {
  if (!contexts.has(userId)) {
    // 超过上限时清理最老的会话
    if (contexts.size >= MAX_CONTEXTS) {
      let oldest = null, oldestKey = null;
      for (const [key, val] of contexts) {
        if (!oldest || val.created < oldest) { oldest = val.created; oldestKey = key; }
      }
      if (oldestKey) contexts.delete(oldestKey);
    }
    contexts.set(userId, { messages: [], count: 0, created: Date.now(), lastActive: Date.now() });
  }
  const ctx = contexts.get(userId);
  ctx.lastActive = Date.now();
  return ctx;
}

// 定期清理过期会话
const _cleanupTimer = setInterval(() => {
  const now = Date.now();
  for (const [key, val] of contexts) {
    if (now - val.lastActive > CONTEXT_TTL_MS) {
      contexts.delete(key);
    }
  }
}, 300000); // 每5分钟清理一次
// 允许进程优雅退出
if (_cleanupTimer.unref) _cleanupTimer.unref();

/**
 * 国内模型智能对话（带广州中继 + 自动降级）
 *
 * 调用链:
 *   1. 广州CN中继（如已配置）→ 国内直连·低延迟
 *   2. 降级: 直连国内API → 跨境·高延迟但可用
 */
async function chat(userId, message) {
  const ctx = getContext(userId);

  // 获取记忆增强的系统提示词
  let systemPrompt = PERSONA_SYSTEM_PROMPT;
  if (personaMemory) {
    try {
      systemPrompt = await personaMemory.buildSystemPrompt(userId);
    } catch (e) {
      console.warn('[国内网关] 记忆加载失败，使用静态提示词:', e.message);
    }
  }

  // 通过上下文管线注入Notion认知层（如果可用）
  let pipelineStatus = { active: false, layers: [] };
  if (contextPipeline) {
    try {
      const pipelineResult = await contextPipeline.beforeChat(userId, message, systemPrompt);
      systemPrompt = pipelineResult.enhancedPrompt;
      pipelineStatus = {
        active: true,
        persona: pipelineResult.persona || 'zhuyuan',
        personaSwitched: !!pipelineResult.personaSwitched,
        devTaskDetected: !!pipelineResult.devTaskDetected,
        turnCount: pipelineResult.session ? pipelineResult.session.turnCount : 0
      };
    } catch (e) {
      console.warn('[国内网关] 上下文管线执行失败，使用基础提示词:', e.message);
      pipelineStatus = { active: false, error: e.message };
    }
  }

  // 组装消息
  const messages = [
    { role: 'system', content: systemPrompt },
    ...ctx.messages.slice(-MAX_HISTORY),
    { role: 'user', content: message }
  ];

  // 智能选择模型
  const selected = selectModel(message, { messageCount: ctx.count });
  if (!selected) {
    return {
      success: false,
      message: '⚠️ 国内模型API未配置，请检查密钥设置。',
      model: 'none'
    };
  }

  // 获取可用模型的降级顺序
  const available = DOMESTIC_MODELS.filter(m => {
    const key = process.env[m.envKey];
    return key && key.length > 5;
  });
  const fallbackOrder = [selected, ...available.filter(m => m.id !== selected.id)].map(m => m.id);

  // ── 优先走广州CN中继（仅限国内服务器区域） ──
  if (USE_CN_RELAY) {
    try {
      const relayResponse = await callViaCNRelay(messages, selected, fallbackOrder);
      const content = relayResponse.choices?.[0]?.message?.content || '铸渊暂时无法回应...';
      const usage = relayResponse.usage || {};

      // 记录上下文
      ctx.messages.push({ role: 'user', content: message });
      ctx.messages.push({ role: 'assistant', content });
      ctx.count++;
      if (ctx.messages.length > MAX_HISTORY * 2) {
        ctx.messages = ctx.messages.slice(-MAX_HISTORY * 2);
      }

      // 统计
      gatewayState.totalCalls++;
      gatewayState.successCalls++;
      const modelId = relayResponse.model_id || selected.id;
      if (!gatewayState.modelStats[modelId]) {
        gatewayState.modelStats[modelId] = { calls: 0, tokens: 0 };
      }
      gatewayState.modelStats[modelId].calls++;
      gatewayState.modelStats[modelId].tokens += (usage.total_tokens || 0);

      // 记录到人格体记忆（异步，不阻塞响应）
      if (personaMemory) {
        personaMemory.recordConversationMemory(userId, message, content);
      }

      // 上下文管线后处理（认知增量入队 + 摘要压缩）
      if (contextPipeline) {
        contextPipeline.afterChat(userId, message, content, ctx.messages);
      }

      return {
        success: true,
        message: content,
        model: selected.name, // 显示实际使用的模型名
        tier: 'economy',
        reason: selected.reason,
        relay: 'cn-relay',
        usage: {
          prompt_tokens: usage.prompt_tokens || 0,
          completion_tokens: usage.completion_tokens || 0
        },
        pipeline: pipelineStatus
      };
    } catch (relayErr) {
      console.error(`[国内网关] 广州中继失败，降级为直连: ${relayErr.message}`);
      // 继续走直连降级路径
    }
  }

  // ── 降级: 直连国内API (从新加坡跨境调用) ──
  let lastError = null;
  const tried = [selected, ...available.filter(m => m.id !== selected.id)];
  const triedLog = [];

  for (const model of tried) {
    try {
      const modelWithParams = { ...model, temperature: selected.temperature, selectedMaxTokens: selected.selectedMaxTokens };
      const response = await callDomesticLLM(modelWithParams, messages);

      const content = response.choices?.[0]?.message?.content || '铸渊暂时无法回应...';
      const usage = response.usage || {};

      // 记录上下文
      ctx.messages.push({ role: 'user', content: message });
      ctx.messages.push({ role: 'assistant', content });
      ctx.count++;
      if (ctx.messages.length > MAX_HISTORY * 2) {
        ctx.messages = ctx.messages.slice(-MAX_HISTORY * 2);
      }

      // 统计
      gatewayState.totalCalls++;
      gatewayState.successCalls++;
      if (!gatewayState.modelStats[model.id]) {
        gatewayState.modelStats[model.id] = { calls: 0, tokens: 0 };
      }
      gatewayState.modelStats[model.id].calls++;
      gatewayState.modelStats[model.id].tokens += (usage.total_tokens || 0);

      // 记录到人格体记忆（异步，不阻塞响应）
      if (personaMemory) {
        personaMemory.recordConversationMemory(userId, message, content);
      }

      // 上下文管线后处理（认知增量入队 + 摘要压缩）
      if (contextPipeline) {
        contextPipeline.afterChat(userId, message, content, ctx.messages);
      }

      return {
        success: true,
        message: content,
        model: model.name, // 显示实际使用的模型名
        tier: model.tier,
        reason: selected.reason,
        relay: 'direct',
        usage: {
          prompt_tokens: usage.prompt_tokens || 0,
          completion_tokens: usage.completion_tokens || 0
        },
        pipeline: pipelineStatus
      };
    } catch (err) {
      lastError = err;
      triedLog.push(`${model.id}: ${err.message}`);
      console.error(`[国内网关] ${model.id} 调用失败: ${err.message}`);
      continue;
    }
  }

  // 所有模型都失败
  gatewayState.totalCalls++;
  gatewayState.failedCalls++;
  gatewayState.lastError = { time: new Date().toISOString(), message: lastError?.message, triedModels: triedLog };

  return {
    success: false,
    message: `⚠️ 铸渊暂时无法回应。已尝试 ${tried.length} 个模型均失败。\n\n请检查 /api/chat/diagnostics 查看详情。\n\n最后错误: ${lastError?.message || '未知'}`,
    model: 'fallback',
    error: lastError?.message,
    triedModels: triedLog
  };
}

/**
 * 获取网关状态
 */
function getGatewayStats() {
  return {
    ...gatewayState,
    uptimeMs: Date.now() - gatewayState.startTime,
    availableModels: DOMESTIC_MODELS.filter(m => {
      const key = process.env[m.envKey];
      return key && key.length > 5;
    }).length,
    totalModels: DOMESTIC_MODELS.length,
    cnRelay: {
      configured: !!(CN_RELAY_HOST && CN_RELAY_KEY),
      host: CN_RELAY_HOST || null,
      port: CN_RELAY_PORT
    }
  };
}

module.exports = {
  chat,
  selectModel,
  getGatewayStats
};
