#!/usr/bin/env node
/**
 * GLADA · 冒烟测试 · glada-smoke.test.js
 *
 * 验证所有模块可以正常加载和基本功能可用。
 *
 * 用法: node glada/tests/glada-smoke.test.js
 *
 * 版权：国作登字-2026-A-00037559
 */

'use strict';

const path = require('path');
const fs = require('fs');

const ROOT = path.resolve(__dirname, '../..');

let passed = 0;
let failed = 0;

function test(name, fn) {
  try {
    fn();
    console.log(`  ✅ ${name}`);
    passed++;
  } catch (err) {
    console.log(`  ❌ ${name}: ${err.message}`);
    failed++;
  }
}

function assert(condition, message) {
  if (!condition) throw new Error(message || 'Assertion failed');
}

console.log('🧪 GLADA 冒烟测试\n');

// ── 预加载模块（E2E测试需要）──
const taskReceiver = require('../task-receiver');
const contextBuilder = require('../context-builder');
const stepExecutor = require('../step-executor');
const notifier = require('../notifier');

// ── 1. 模块加载测试 ──

console.log('📦 模块加载:');

test('task-receiver 加载', () => {
  const mod = require('../task-receiver');
  assert(typeof mod.scanPendingTasks === 'function', 'scanPendingTasks 不是函数');
  assert(typeof mod.validateTaskSpec === 'function', 'validateTaskSpec 不是函数');
  assert(typeof mod.convertToGladaTask === 'function', 'convertToGladaTask 不是函数');
  assert(typeof mod.receiveNextTask === 'function', 'receiveNextTask 不是函数');
  assert(typeof mod.updateTask === 'function', 'updateTask 不是函数');
  assert(typeof mod.archiveTask === 'function', 'archiveTask 不是函数');
});

test('context-builder 加载', () => {
  const mod = require('../context-builder');
  assert(typeof mod.buildContext === 'function', 'buildContext 不是函数');
  assert(typeof mod.contextToSystemPrompt === 'function', 'contextToSystemPrompt 不是函数');
  assert(typeof mod.scanTargetFiles === 'function', 'scanTargetFiles 不是函数');
  assert(typeof mod.scanDependencies === 'function', 'scanDependencies 不是函数');
});

test('step-executor 加载', () => {
  const mod = require('../step-executor');
  assert(typeof mod.executeStep === 'function', 'executeStep 不是函数');
  assert(typeof mod.parseFileChanges === 'function', 'parseFileChanges 不是函数');
  assert(typeof mod.snapshotFiles === 'function', 'snapshotFiles 不是函数');
  assert(typeof mod.applyFileChanges === 'function', 'applyFileChanges 不是函数');
});

test('code-generator 加载', () => {
  const mod = require('../code-generator');
  assert(typeof mod.findDependents === 'function', 'findDependents 不是函数');
  assert(typeof mod.regressionCheck === 'function', 'regressionCheck 不是函数');
  assert(typeof mod.executeWithRegressionGuard === 'function', 'executeWithRegressionGuard 不是函数');
});

test('git-operator 加载', () => {
  const mod = require('../git-operator');
  assert(typeof mod.getCurrentBranch === 'function', 'getCurrentBranch 不是函数');
  assert(typeof mod.createTaskBranch === 'function', 'createTaskBranch 不是函数');
  assert(typeof mod.commitStep === 'function', 'commitStep 不是函数');
});

test('notifier 加载', () => {
  const mod = require('../notifier');
  assert(typeof mod.notify === 'function', 'notify 不是函数');
  assert(typeof mod.buildNotification === 'function', 'buildNotification 不是函数');
  assert(typeof mod.sendEmail === 'function', 'sendEmail 不是函数');
  assert(typeof mod.sendWeCom === 'function', 'sendWeCom 不是函数');
});

test('execution-loop 加载', () => {
  const mod = require('../execution-loop');
  assert(typeof mod.executeTask === 'function', 'executeTask 不是函数');
  assert(typeof mod.startLoop === 'function', 'startLoop 不是函数');
});

// ── 2. 功能测试 ──

console.log('\n⚙️ 功能测试:');

test('任务规格验证 - 合法', () => {
  const { validateTaskSpec } = require('../task-receiver');
  const result = validateTaskSpec({
    task_id: 'CAB-20260417-001',
    authorization: { sovereign: '冰朔 · TCS-0002∞' },
    development_plan: {
      title: '测试任务',
      steps: ['步骤1', '步骤2']
    }
  });
  assert(result.valid === true, `应该是合法的，但返回: ${JSON.stringify(result)}`);
});

test('任务规格验证 - 非法（无授权）', () => {
  const { validateTaskSpec } = require('../task-receiver');
  const result = validateTaskSpec({
    task_id: 'CAB-20260417-001',
    authorization: { sovereign: '其他人' },
    development_plan: { title: '测试', steps: ['步骤1'] }
  });
  assert(result.valid === false, '应该是非法的');
  assert(result.errors.length > 0, '应该有错误信息');
});

test('任务规格验证 - 非法（无步骤）', () => {
  const { validateTaskSpec } = require('../task-receiver');
  const result = validateTaskSpec({
    task_id: 'CAB-20260417-001',
    authorization: { sovereign: '冰朔 · TCS-0002∞' },
    development_plan: { title: '测试', steps: [] }
  });
  assert(result.valid === false, '应该是非法的');
});

test('CAB → GLADA 任务转换', () => {
  const { convertToGladaTask } = require('../task-receiver');
  const spec = {
    task_id: 'CAB-20260417-001',
    authorization: { sovereign: '冰朔 · TCS-0002∞' },
    development_plan: {
      title: '测试项目',
      description: '这是一个测试',
      steps: ['创建文件', '修改配置'],
      priority: 'high'
    },
    architecture: { summary: '架构描述' },
    constraints: { no_touch_files: ['.github/'], required_tests: true }
  };

  const task = convertToGladaTask(spec);
  assert(task.glada_task_id === 'GLADA-CAB-20260417-001', `任务ID错误: ${task.glada_task_id}`);
  assert(task.plan.steps.length === 2, `步骤数错误: ${task.plan.steps.length}`);
  assert(task.plan.steps[0].step_id === 1, '步骤1 ID错误');
  assert(task.plan.steps[0].description === '创建文件', '步骤1 描述错误');
  assert(task.plan.priority === 'high', '优先级错误');
  assert(task.status === 'pending', '状态错误');
  assert(Array.isArray(task.execution_log), '缺少 execution_log');
});

test('LLM 输出解析 - JSON 代码块', () => {
  const { parseFileChanges } = require('../step-executor');
  const output = '这是一些解释\n```json\n{"reasoning":"测试","files":[{"path":"test.txt","action":"create","content":"hello"}],"summary":"创建测试文件"}\n```\n更多解释';
  const result = parseFileChanges(output);
  assert(result !== null, '解析失败');
  assert(result.files.length === 1, `文件数错误: ${result.files.length}`);
  assert(result.files[0].path === 'test.txt', '路径错误');
  assert(result.reasoning === '测试', '推理错误');
});

test('LLM 输出解析 - 纯 JSON', () => {
  const { parseFileChanges } = require('../step-executor');
  const output = '{"reasoning":"纯JSON","files":[{"path":"a.js","action":"modify","content":"code"}],"summary":"修改"}';
  const result = parseFileChanges(output);
  assert(result !== null, '解析失败');
  assert(result.files[0].action === 'modify', '操作错误');
});

test('LLM 输出解析 - 无效格式', () => {
  const { parseFileChanges } = require('../step-executor');
  const result = parseFileChanges('这是一段普通文本');
  assert(result === null, '应该返回 null');
});

test('文件快照和恢复', () => {
  const { snapshotFiles, restoreFromSnapshot } = require('../step-executor');

  // 创建测试文件
  const testFile = '/tmp/glada-test-snapshot.txt';
  fs.writeFileSync(testFile, 'original content', 'utf-8');

  // 拍摄快照（使用相对路径模拟）
  const snapshot = new Map();
  snapshot.set(testFile, 'original content');

  // 修改文件
  fs.writeFileSync(testFile, 'modified content', 'utf-8');
  assert(fs.readFileSync(testFile, 'utf-8') === 'modified content', '文件没有被修改');

  // 清理
  fs.unlinkSync(testFile);
});

test('上下文构建', () => {
  const { buildContext, contextToSystemPrompt } = require('../context-builder');
  const mockTask = {
    glada_task_id: 'GLADA-TEST-001',
    plan: {
      title: '测试任务',
      description: '测试描述',
      steps: [{ step_id: 1, description: '步骤1', status: 'pending' }]
    },
    architecture: { summary: '测试架构', target_files: [] },
    constraints: {},
    reasoning_context: {},
    execution_log: []
  };

  const context = buildContext(mockTask);
  assert(context.task_id === 'GLADA-TEST-001', 'task_id 不匹配');
  assert(context.sections.identity, '缺少 identity section');
  assert(context.sections.task, '缺少 task section');

  const prompt = contextToSystemPrompt(context);
  assert(typeof prompt === 'string', 'prompt 不是字符串');
  assert(prompt.includes('铸渊'), 'prompt 缺少铸渊身份');
});

test('通知内容构建', () => {
  const { buildNotification } = require('../notifier');
  const mockTask = {
    glada_task_id: 'GLADA-TEST-001',
    plan: {
      title: '测试任务',
      steps: [
        { step_id: 1, description: '步骤1', status: 'completed' },
        { step_id: 2, description: '步骤2', status: 'pending' }
      ]
    },
    completion: {
      total_files_changed: ['a.js', 'b.js'],
      git_branch: 'glada/test',
      git_commits: ['abc1234: 步骤1']
    }
  };

  const notif = buildNotification(mockTask, 'completed');
  assert(notif.subject.includes('GLADA-TEST-001'), '主题缺少任务ID');
  assert(notif.body.includes('测试任务'), '正文缺少任务标题');
  assert(notif.body.includes('a.js'), '正文缺少变更文件');
  assert(notif.html, '缺少 HTML 邮件内容');
  assert(notif.html.includes('GLADA-TEST-001'), 'HTML 缺少任务ID');
  assert(notif.html.includes('a.js'), 'HTML 缺少变更文件');
  assert(notif.html.includes('铸渊'), 'HTML 缺少铸渊签名');
});

test('文件依赖扫描', () => {
  const { scanDependencies } = require('../context-builder');
  // 扫描一个已知文件的依赖
  const serverPath = path.join(ROOT, 'server.js');
  if (fs.existsSync(serverPath)) {
    const deps = scanDependencies(serverPath);
    assert(Array.isArray(deps), 'deps 不是数组');
  }
});

test('中央任务队列 glada-task 类型已注册', () => {
  const taskQueuePath = path.join(ROOT, 'core', 'task-queue', 'index.js');
  assert(fs.existsSync(taskQueuePath), 'core/task-queue/index.js 不存在');
  const content = fs.readFileSync(taskQueuePath, 'utf-8');
  assert(content.includes("'glada-task'"), 'glada-task 类型未注册到中央任务队列');
});

test('CAB 任务模板包含 execution_plan 字段', () => {
  const templatePath = path.join(ROOT, 'bridge', 'chat-to-agent', 'task-template.json');
  assert(fs.existsSync(templatePath), 'task-template.json 不存在');
  const template = JSON.parse(fs.readFileSync(templatePath, 'utf-8'));
  assert(template.execution_plan, '缺少 execution_plan 字段');
  assert(template.execution_plan.executor === 'glada', 'executor 应为 glada');
  assert(template.execution_plan.rollback_on_failure === true, 'rollback_on_failure 应为 true');
});

test('ecosystem.config.js 包含 env 加载逻辑', () => {
  const ecosystemPath = path.join(ROOT, 'glada', 'ecosystem.config.js');
  const content = fs.readFileSync(ecosystemPath, 'utf-8');
  assert(content.includes('loadEnvFile'), 'ecosystem.config.js 缺少 loadEnvFile 函数');
  assert(content.includes('.env.glada'), '缺少 .env.glada 加载');
});

test('execution-loop 集成中央任务队列', () => {
  const loopPath = path.join(ROOT, 'glada', 'execution-loop.js');
  const content = fs.readFileSync(loopPath, 'utf-8');
  assert(content.includes('core/task-queue'), '缺少 core/task-queue 集成');
  assert(content.includes('dequeueFromCentralQueue'), '缺少 dequeueFromCentralQueue 函数');
  assert(content.includes('glada-task'), '缺少 glada-task 类型检查');
});

// ── E2E 集成测试 ──

test('E2E: CAB任务提交→转换→入队→状态查询完整链路', () => {
  const cabSpec = {
    cab_version: '1.0',
    task_id: 'CAB-20260417-999',
    created_at: new Date().toISOString(),
    created_by: 'TCS-0002∞',
    status: 'pending',
    authorization: {
      sovereign: '冰朔 · TCS-0002∞',
      authorized_agent: 'copilot-agent',
      scope: 'full-auto-development'
    },
    architecture: {
      summary: 'E2E 测试任务',
      target_files: ['glada/README.md'],
      target_modules: ['glada']
    },
    development_plan: {
      title: 'E2E 集成测试任务',
      description: '验证完整链路',
      steps: ['步骤1: 检查文件', '步骤2: 修改文件'],
      priority: 'normal'
    },
    execution_plan: {
      executor: 'glada',
      model_preference: 'deepseek-chat',
      retry_policy: { max_retries: 2, backoff_ms: 1000 }
    },
    constraints: {
      no_touch_files: ['.github/brain/'],
      required_tests: false,
      max_files_changed: 5
    }
  };

  // 1. 验证任务规格
  const validation = taskReceiver.validateTaskSpec(cabSpec);
  assert(validation.valid, `验证失败: ${validation.errors.join(', ')}`);

  // 2. 转换为 GLADA 任务
  const gladaTask = taskReceiver.convertToGladaTask(cabSpec);
  assert(gladaTask.glada_task_id === 'GLADA-CAB-20260417-999', 'ID 转换错误');
  assert(gladaTask.plan.steps.length === 2, '步骤数应为 2');
  assert(gladaTask.plan.steps[0].status === 'pending', '步骤状态应为 pending');
  assert(gladaTask.constraints.no_touch_files.length === 1, '约束丢失');

  // 3. 保存到临时队列并读回
  const tmpQueueDir = '/tmp/glada-e2e-test-queue';
  fs.mkdirSync(tmpQueueDir, { recursive: true });
  const queueFile = path.join(tmpQueueDir, `${gladaTask.glada_task_id}.json`);
  fs.writeFileSync(queueFile, JSON.stringify(gladaTask, null, 2), 'utf-8');

  const readBack = JSON.parse(fs.readFileSync(queueFile, 'utf-8'));
  assert(readBack.glada_task_id === gladaTask.glada_task_id, '队列读回不一致');
  assert(readBack.plan.title === 'E2E 集成测试任务', '标题不一致');

  // 4. 构建上下文
  const context = contextBuilder.buildContext(gladaTask);
  assert(context.sections.identity, '上下文缺少 identity');
  assert(context.sections.task, '上下文缺少 task');

  const systemPrompt = contextBuilder.contextToSystemPrompt(context);
  assert(systemPrompt.length > 100, '系统提示词过短');
  assert(systemPrompt.includes('铸渊'), '提示词应包含铸渊身份');

  // 5. 构建步骤提示词
  const stepPrompt = stepExecutor.buildStepPrompt(gladaTask.plan.steps[0], gladaTask);
  assert(stepPrompt.includes('步骤 1'), '步骤提示词应包含步骤编号');
  assert(stepPrompt.includes('JSON'), '步骤提示词应要求 JSON 输出');

  // 6. 验证通知构建
  gladaTask.status = 'completed';
  gladaTask.completion = {
    total_files_changed: ['test.js'],
    git_branch: 'glada/test',
    git_commits: ['abc123']
  };
  const notification = notifier.buildNotification(gladaTask, 'completed');
  assert(notification.subject.includes('GLADA'), '通知标题应包含 GLADA');
  assert(notification.body.includes('test.js'), '通知正文应包含变更文件');

  // 清理
  fs.unlinkSync(queueFile);
  try { fs.rmdirSync(tmpQueueDir); } catch { /* ok */ }
});

test('step-executor 支持 LLM 重试配置', () => {
  const content = fs.readFileSync(path.join(ROOT, 'glada', 'step-executor.js'), 'utf-8');
  assert(content.includes('maxRetries'), '缺少 maxRetries 参数');
  assert(content.includes('backoffMs'), '缺少 backoffMs 参数');
  assert(content.includes('execution_plan'), '缺少 execution_plan 集成');
  assert(content.includes('retry_policy'), '缺少 retry_policy 读取');
});

test('service.js 包含速率限制', () => {
  const content = fs.readFileSync(path.join(ROOT, 'glada', 'service.js'), 'utf-8');
  assert(content.includes('rateLimit'), '缺少速率限制函数');
  assert(content.includes('429'), '缺少 429 状态码');
});

test('package.json 包含 express 依赖', () => {
  const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'glada', 'package.json'), 'utf-8'));
  assert(pkg.dependencies && pkg.dependencies.express, '缺少 express 依赖');
});

test('package.json 包含 nodemailer 依赖', () => {
  const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'glada', 'package.json'), 'utf-8'));
  assert(pkg.dependencies && pkg.dependencies.nodemailer, '缺少 nodemailer 依赖');
});

test('notifier.js 已替换钉钉为邮箱+企业微信', () => {
  const content = fs.readFileSync(path.join(ROOT, 'glada', 'notifier.js'), 'utf-8');
  // 应包含邮箱通道
  assert(content.includes('sendEmail'), '缺少 sendEmail 函数');
  assert(content.includes('nodemailer'), '缺少 nodemailer 引用');
  assert(content.includes('smtp.qq.com'), '缺少 QQ 邮箱 SMTP 配置');
  assert(content.includes('ZY_SMTP_USER'), '缺少 ZY_SMTP_USER 环境变量');
  // 应包含企业微信预留通道
  assert(content.includes('sendWeCom'), '缺少 sendWeCom 函数');
  assert(content.includes('WECOM_WEBHOOK'), '缺少 WECOM_WEBHOOK 环境变量');
  // 不应包含钉钉
  assert(!content.includes('sendDingTalk'), '仍包含已废弃的 sendDingTalk');
  assert(!content.includes('DINGTALK_WEBHOOK'), '仍包含已废弃的 DINGTALK_WEBHOOK');
});

test('通知 HTML 邮件模板包含光湖视觉风格', () => {
  const { buildNotification } = require('../notifier');
  const mockTask = {
    glada_task_id: 'GLADA-TEST-HTML',
    plan: {
      title: 'HTML模板测试',
      steps: [
        { step_id: 1, description: '步骤A', status: 'completed' },
        { step_id: 2, description: '步骤B', status: 'failed', error: '测试错误' }
      ]
    },
    completion: { total_files_changed: ['test.js'], git_branch: 'glada/html-test', git_commits: ['hash: msg'] }
  };

  // 测试 completed
  const completed = buildNotification(mockTask, 'completed');
  assert(completed.html.includes('<!DOCTYPE html>'), 'HTML 缺少 DOCTYPE');
  assert(completed.html.includes('渊'), 'HTML 缺少铸渊标识');
  assert(completed.html.includes('#050810'), 'HTML 缺少光湖暗色背景');

  // 测试 failed
  const failed = buildNotification(mockTask, 'failed');
  assert(failed.html.includes('f87171'), 'failed HTML 应包含红色');
  assert(failed.subject.includes('❌'), 'failed 主题应包含 ❌');

  // 测试 started
  const started = buildNotification(mockTask, 'started');
  assert(started.html.includes('🚀'), 'started HTML 应包含 🚀');
});

test('deploy workflow 注入 SMTP 到 .env.glada', () => {
  const content = fs.readFileSync(path.join(ROOT, '.github', 'workflows', 'deploy-to-zhuyuan-server.yml'), 'utf-8');
  assert(content.includes('ZY_SMTP_USER') && content.includes('.env.glada'), '部署 workflow 缺少 SMTP 注入到 .env.glada');
});

test('install-check.js 预飞检查工具存在', () => {
  const checkPath = path.join(ROOT, 'glada', 'install-check.js');
  assert(fs.existsSync(checkPath), 'install-check.js 不存在');
  const content = fs.readFileSync(checkPath, 'utf-8');
  assert(content.includes('ZY_LLM_API_KEY'), '预飞检查应检查 API Key');
  assert(content.includes('ZY_LLM_BASE_URL'), '预飞检查应检查 Base URL');
  assert(content.includes('testLLMConnection'), '预飞检查应测试 LLM 连通性');
  assert(content.includes('ZY_SMTP_USER'), '预飞检查应检查 SMTP');
  assert(content.includes('WECOM_WEBHOOK'), '预飞检查应检查企业微信');
});

// ── 3. Skill 蒸馏测试（Hermes-inspired · Phase 1） ──

console.log('\n🧪 Skill 蒸馏测试:');

test('skill-distiller 模块加载', () => {
  const mod = require('../skill-distiller');
  assert(typeof mod.distillSkill === 'function', 'distillSkill 不是函数');
  assert(typeof mod.saveSkill === 'function', 'saveSkill 不是函数');
  assert(typeof mod.loadAllSkills === 'function', 'loadAllSkills 不是函数');
  assert(typeof mod.findRelevantSkills === 'function', 'findRelevantSkills 不是函数');
  assert(typeof mod.skillsToContext === 'function', 'skillsToContext 不是函数');
  assert(typeof mod.distillAndSave === 'function', 'distillAndSave 不是函数');
  assert(typeof mod.extractTags === 'function', 'extractTags 不是函数');
});

test('Skill 蒸馏 - 成功任务生成 HNL 格式 skill', () => {
  const { distillSkill } = require('../skill-distiller');
  const mockTask = {
    glada_task_id: 'GLADA-CAB-20260418-001',
    status: 'completed',
    plan: {
      title: '添加用户认证 API',
      description: '实现 login 和 register 接口',
      steps: [
        { step_id: 1, description: '创建 schema', status: 'completed', files_changed: ['src/schemas/hli/auth/login.schema.json'], reasoning: 'HLI 协议要求 schema 优先' },
        { step_id: 2, description: '创建路由', status: 'completed', files_changed: ['src/routes/hli/auth/login.js'], reasoning: '路由遵循 HLI 约定' }
      ]
    },
    execution_log: [
      { step_id: 1, action: '创建 schema', status: 'completed', reasoning: 'HLI 协议要求', files_changed: ['src/schemas/hli/auth/login.schema.json'], duration_ms: 5000 },
      { step_id: 2, action: '创建路由', status: 'completed', reasoning: '遵循约定', files_changed: ['src/routes/hli/auth/login.js'], duration_ms: 8000 }
    ],
    architecture: { target_files: ['src/routes/hli/auth/'], target_modules: ['auth'] },
    constraints: { no_touch_files: ['.github/'], required_tests: true }
  };

  const skill = distillSkill(mockTask);
  assert(skill !== null, 'skill 不应为 null');
  assert(skill.hnl_v === '1.0', 'HNL 版本应为 1.0');
  assert(skill.type === 'SKILL', '类型应为 SKILL');
  assert(skill.from === 'YM001/ZY001', '发送者应为铸渊');
  assert(skill.skill.name === '添加用户认证 API', 'skill 名称错误');
  assert(skill.skill.step_patterns.length === 2, '步骤模式数量错误');
  assert(skill.skill.success_rate === 1, '成功率应为 1');
  assert(skill.skill.file_domain.length === 2, '文件域数量错误');
  assert(skill.memory_sovereignty.can_forget === true, '记忆主权：应可遗忘');
  assert(skill.op.includes('GROW'), '操作应为 GROW');
});

test('Skill 蒸馏 - 未完成任务返回 null', () => {
  const { distillSkill } = require('../skill-distiller');
  const result = distillSkill({ status: 'failed', plan: { steps: [] }, execution_log: [] });
  assert(result === null, '未完成任务应返回 null');
});

test('Skill 蒸馏 - 部分失败任务包含教训', () => {
  const { distillSkill } = require('../skill-distiller');
  const mockTask = {
    glada_task_id: 'GLADA-CAB-20260418-002',
    status: 'completed',
    plan: {
      title: '数据库迁移',
      steps: [
        { step_id: 1, description: '备份数据', status: 'completed', files_changed: ['scripts/backup.sh'] },
        { step_id: 2, description: '迁移表', status: 'failed', error: 'SQL 语法错误' },
        { step_id: 3, description: '验证迁移', status: 'completed', files_changed: ['tests/migration.test.js'] }
      ]
    },
    execution_log: [],
    architecture: {},
    constraints: {}
  };

  const skill = distillSkill(mockTask);
  assert(skill !== null, 'skill 不应为 null');
  assert(skill.skill.lessons_learned.length === 1, '应有 1 条教训');
  assert(skill.skill.lessons_learned[0].error === 'SQL 语法错误', '教训应包含错误信息');
  assert(skill.skill.success_rate < 1, '成功率应小于 1');
});

test('Skill 标签提取', () => {
  const { extractTags } = require('../skill-distiller');
  const tags = extractTags('添加 HLI API 认证中间件', [
    { description: '创建 auth middleware', files_changed: ['src/middleware/auth.js'] }
  ]);
  assert(Array.isArray(tags), 'tags 应为数组');
  assert(tags.length > 0, 'tags 不应为空');
  assert(tags.includes('middleware'), '应包含 middleware 标签');
  assert(tags.includes('src'), '应包含 src 模块标签');
});

test('Skill 查找 - 按文件域匹配', () => {
  const { findRelevantSkills, saveSkill, SKILLS_DIR } = require('../skill-distiller');

  // 创建临时 skill
  const tmpSkill = {
    type: 'SKILL',
    id: 'SKILL-TEST-MATCH',
    skill: {
      name: '测试 Skill',
      tags: ['glada', 'test'],
      success_rate: 1,
      step_patterns: [],
      file_domain: ['glada/notifier.js', 'glada/service.js'],
      lessons_learned: [],
      architecture_context: { target_files: ['glada/'], target_modules: ['glada'] }
    }
  };

  // 保存测试 skill
  fs.mkdirSync(SKILLS_DIR, { recursive: true });
  const testPath = path.join(SKILLS_DIR, 'SKILL-TEST-MATCH.json');
  fs.writeFileSync(testPath, JSON.stringify(tmpSkill, null, 2), 'utf-8');

  // 查找匹配
  const results = findRelevantSkills({
    architecture: { target_files: ['glada/notifier.js'], target_modules: ['glada'] },
    plan: { title: 'GLADA 通知改进' }
  });

  // 清理
  try { fs.unlinkSync(testPath); } catch { /* ok */ }

  assert(results.length > 0, '应找到匹配的 skill');
  assert(results[0].skill.name === '测试 Skill', '匹配的 skill 名称错误');
});

test('Skill 格式化为上下文', () => {
  const { skillsToContext } = require('../skill-distiller');

  const mockSkills = [{
    skill: {
      name: '测试上下文 Skill',
      source_task: 'GLADA-TEST-001',
      success_rate: 0.85,
      step_patterns: [
        { description: '创建文件', reasoning: '规范要求', files_changed: ['test.js'] }
      ],
      lessons_learned: [
        { step_description: '编译', error: '类型错误' }
      ],
      file_domain: ['test.js']
    }
  }];

  const context = skillsToContext(mockSkills);
  assert(context.includes('测试上下文 Skill'), '上下文应包含 skill 名称');
  assert(context.includes('85%'), '上下文应包含成功率');
  assert(context.includes('教训'), '上下文应包含教训');
  assert(context.includes('test.js'), '上下文应包含文件');
});

test('skillsToContext 空 skills 返回空字符串', () => {
  const { skillsToContext } = require('../skill-distiller');
  assert(skillsToContext([]) === '', '空 skills 应返回空字符串');
  assert(skillsToContext(null) === '', 'null 应返回空字符串');
});

// ── 4. 自我反思测试（Hermes-inspired · Phase 2） ──

console.log('\n🪞 自我反思测试:');

test('reflector 模块加载', () => {
  const mod = require('../reflector');
  assert(typeof mod.buildReflectionPrompt === 'function', 'buildReflectionPrompt 不是函数');
  assert(typeof mod.parseReflection === 'function', 'parseReflection 不是函数');
  assert(typeof mod.wrapAsHNL === 'function', 'wrapAsHNL 不是函数');
  assert(typeof mod.reflectOffline === 'function', 'reflectOffline 不是函数');
  assert(typeof mod.reflectWithLLM === 'function', 'reflectWithLLM 不是函数');
  assert(typeof mod.loadRecentReflections === 'function', 'loadRecentReflections 不是函数');
});

test('反思提示词构建', () => {
  const { buildReflectionPrompt } = require('../reflector');
  const mockTask = {
    glada_task_id: 'GLADA-TEST-REFLECT',
    status: 'completed',
    plan: {
      title: '反思测试任务',
      description: '用于测试反思功能',
      steps: [
        { step_id: 1, description: '步骤1', status: 'completed', reasoning: '推理1', files_changed: ['a.js'] },
        { step_id: 2, description: '步骤2', status: 'failed', error: '测试错误' }
      ]
    },
    execution_log: [
      { step_id: 1, action: '步骤1', status: 'completed', reasoning: '推理1', files_changed: ['a.js'], duration_ms: 3000 },
      { step_id: 2, action: '步骤2', status: 'failed', error: '测试错误', duration_ms: 2000 }
    ],
    constraints: { no_touch_files: ['.github/'] }
  };

  const { systemPrompt, userMessage } = buildReflectionPrompt(mockTask);
  assert(systemPrompt.includes('铸渊'), '系统提示词应包含铸渊身份');
  assert(systemPrompt.includes('JSON'), '系统提示词应要求 JSON 输出');
  assert(userMessage.includes('GLADA-TEST-REFLECT'), '用户消息应包含任务ID');
  assert(userMessage.includes('反思测试任务'), '用户消息应包含任务标题');
  assert(userMessage.includes('测试错误'), '用户消息应包含失败错误');
});

test('反思 JSON 解析 - 标准格式', () => {
  const { parseReflection } = require('../reflector');
  const output = '```json\n{"what_went_well":["步骤1成功"],"what_went_wrong":["步骤2失败"],"improvements":["增加检查"],"patterns_discovered":["模式A"],"confidence_score":0.75}\n```';
  const result = parseReflection(output);
  assert(result !== null, '解析不应为 null');
  assert(result.what_went_well.length === 1, 'what_went_well 长度错误');
  assert(result.what_went_wrong.length === 1, 'what_went_wrong 长度错误');
  assert(result.improvements.length === 1, 'improvements 长度错误');
  assert(result.confidence_score === 0.75, '信心评分错误');
});

test('反思 JSON 解析 - 异常值安全处理', () => {
  const { parseReflection } = require('../reflector');
  const output = '{"what_went_well":"not_array","confidence_score":999}';
  const result = parseReflection(output);
  assert(result !== null, '解析不应为 null');
  assert(Array.isArray(result.what_went_well), 'what_went_well 应降级为数组');
  assert(result.what_went_well.length === 0, '非数组应降级为空数组');
  assert(result.confidence_score === 1, '超出范围的分数应 clamp 到 1');
});

test('反思 JSON 解析 - 无效输入返回 null', () => {
  const { parseReflection } = require('../reflector');
  assert(parseReflection('这不是 JSON') === null, '纯文本应返回 null');
});

test('反思 HNL 包装', () => {
  const { wrapAsHNL } = require('../reflector');
  const mockTask = {
    glada_task_id: 'GLADA-TEST-HNL',
    status: 'completed',
    plan: { title: 'HNL 包装测试' }
  };
  const reflectionData = {
    what_went_well: ['很好'],
    what_went_wrong: [],
    improvements: ['可以更好'],
    patterns_discovered: ['模式1'],
    confidence_score: 0.9
  };

  const doc = wrapAsHNL(mockTask, reflectionData);
  assert(doc.hnl_v === '1.0', 'HNL 版本应为 1.0');
  assert(doc.type === 'GROW', '类型应为 GROW');
  assert(doc.from === 'YM001/ZY001', '发送者应为铸渊');
  assert(doc.to === 'YM001/ZY001', '接收者应为铸渊（自我反思）');
  assert(doc.op.includes('GROW'), '操作应为 GROW');
  assert(doc.payload.intent === 'task_reflection', '意图应为 task_reflection');
  assert(doc.payload.data.confidence_score === 0.9, '信心评分应传递');
  assert(doc.memory_sovereignty.can_forget === true, '记忆主权：应可遗忘');
});

test('离线反思 - 全部成功的任务', () => {
  const { reflectOffline, REFLECTIONS_DIR } = require('../reflector');
  const mockTask = {
    glada_task_id: 'GLADA-TEST-OFFLINE-OK',
    status: 'completed',
    plan: {
      title: '离线反思成功测试',
      steps: [
        { step_id: 1, description: '步骤1', status: 'completed', files_changed: ['a.js'], reasoning: '推理1' },
        { step_id: 2, description: '步骤2', status: 'completed', files_changed: ['b.js'] }
      ]
    },
    execution_log: [
      { step_id: 1, action: '步骤1', status: 'completed', reasoning: '推理1', files_changed: ['a.js'], duration_ms: 1000 },
      { step_id: 2, action: '步骤2', status: 'completed', files_changed: ['b.js'], duration_ms: 2000 }
    ],
    constraints: {}
  };

  const result = reflectOffline(mockTask);
  assert(result.saved === true, '应保存成功');
  assert(result.reflection.confidence_score === 1, '全部成功时信心应为 1');
  assert(result.reflection.what_went_well.length === 2, '应有 2 条好的');
  assert(result.reflection.what_went_wrong.length === 0, '不应有差的');
  assert(result.hnlDoc.hnl_v === '1.0', 'HNL 文档版本应为 1.0');

  // 清理
  try { fs.unlinkSync(result.path); } catch { /* ok */ }
});

test('离线反思 - 有失败步骤的任务', () => {
  const { reflectOffline } = require('../reflector');
  const mockTask = {
    glada_task_id: 'GLADA-TEST-OFFLINE-FAIL',
    status: 'failed',
    plan: {
      title: '离线反思失败测试',
      steps: [
        { step_id: 1, description: '成功步骤', status: 'completed', files_changed: ['ok.js'] },
        { step_id: 2, description: '失败步骤', status: 'failed', error: 'SQL 错误' },
        { step_id: 3, description: '回滚步骤', status: 'rolled_back', error: '测试失败' }
      ]
    },
    execution_log: [
      { step_id: 1, action: '成功步骤', status: 'completed', files_changed: ['ok.js'] },
      { step_id: 2, action: '失败步骤', status: 'failed', error: 'SQL 错误' },
      { step_id: 3, action: '回滚步骤', status: 'rolled_back', error: '测试失败' }
    ],
    constraints: {}
  };

  const result = reflectOffline(mockTask);
  assert(result.saved === true, '应保存成功');
  assert(result.reflection.confidence_score < 1, '有失败时信心应小于 1');
  assert(result.reflection.what_went_wrong.length === 2, '应有 2 条差的');
  assert(result.reflection.improvements.length >= 2, '应有改进建议');

  // 清理
  try { fs.unlinkSync(result.path); } catch { /* ok */ }
});

test('execution-loop 集成 skill-distiller 和 reflector', () => {
  const loopContent = fs.readFileSync(path.join(ROOT, 'glada', 'execution-loop.js'), 'utf-8');
  assert(loopContent.includes("require('./skill-distiller')"), 'execution-loop 应引用 skill-distiller');
  assert(loopContent.includes("require('./reflector')"), 'execution-loop 应引用 reflector');
  assert(loopContent.includes('distillAndSave'), 'execution-loop 应调用 distillAndSave');
  assert(loopContent.includes('reflectOffline'), 'execution-loop 应调用 reflectOffline');
});

test('context-builder 集成 skills 加载', () => {
  const ctxContent = fs.readFileSync(path.join(ROOT, 'glada', 'context-builder.js'), 'utf-8');
  assert(ctxContent.includes('skill-distiller'), 'context-builder 应引用 skill-distiller');
  assert(ctxContent.includes('findRelevantSkills'), 'context-builder 应调用 findRelevantSkills');
  assert(ctxContent.includes('skillsToContext'), 'context-builder 应调用 skillsToContext');
  assert(ctxContent.includes('sections.skills'), 'context-builder 应有 skills section');
});

test('context-builder 将 skills 加入系统提示词', () => {
  const ctxContent = fs.readFileSync(path.join(ROOT, 'glada', 'context-builder.js'), 'utf-8');
  assert(ctxContent.includes('context.sections.skills'), 'contextToSystemPrompt 应包含 skills 输出');
});

// ── 5. 模型自动路由测试（第三方代理多模型支持） ──

console.log('\n🔀 模型路由测试:');

test('model-router 模块加载', () => {
  const router = require('../model-router');
  assert(typeof router.initialize === 'function', '应有 initialize 方法');
  assert(typeof router.selectModel === 'function', '应有 selectModel 方法');
  assert(typeof router.detectTaskType === 'function', '应有 detectTaskType 方法');
  assert(typeof router.classifyModels === 'function', '应有 classifyModels 方法');
  assert(typeof router.getStatus === 'function', '应有 getStatus 方法');
  assert(typeof router.shutdown === 'function', '应有 shutdown 方法');
});

test('模型分类 - 推理型模型识别', () => {
  const router = require('../model-router');
  const models = ['deepseek-r1', 'claude-3-opus', 'gpt-4o', 'qwen-turbo'];
  const classified = router.classifyModels(models);
  assert(classified.reasoning.includes('deepseek-r1'), 'deepseek-r1 应被分为推理型');
  assert(classified.reasoning.length > 0, '应有推理型模型');
});

test('模型分类 - 代码型模型识别', () => {
  const router = require('../model-router');
  const models = ['deepseek-coder-v2', 'codestral-latest', 'gpt-4o', 'llama-3'];
  const classified = router.classifyModels(models);
  assert(classified.coding.includes('deepseek-coder-v2'), 'deepseek-coder-v2 应被分为代码型');
  assert(classified.coding.includes('codestral-latest'), 'codestral-latest 应被分为代码型');
});

test('模型分类 - 经济型模型识别', () => {
  const router = require('../model-router');
  const models = ['gpt-4o-mini', 'gemini-2-flash', 'claude-3-haiku', 'qwen-turbo'];
  const classified = router.classifyModels(models);
  assert(classified.economy.includes('gpt-4o-mini'), 'gpt-4o-mini 应被分为经济型');
  assert(classified.economy.length >= 3, '应有多个经济型模型');
});

test('模型分类 - 通用型模型识别', () => {
  const router = require('../model-router');
  const models = ['gpt-4o', 'claude-3-sonnet', 'deepseek-chat', 'qwen-max', 'gemini-pro'];
  const classified = router.classifyModels(models);
  assert(classified.general.length > 0, '应有通用型模型');
  assert(classified.general.includes('gpt-4o'), 'gpt-4o 应被分为通用型');
});

test('任务类型检测 - 代码任务', () => {
  const router = require('../model-router');
  assert(router.detectTaskType('创建新的路由文件') === 'coding', '创建路由应为 coding');
  assert(router.detectTaskType('修改数据库模块') === 'coding', '修改模块应为 coding');
  assert(router.detectTaskType('implement the API endpoint') === 'coding', 'implement 应为 coding');
});

test('任务类型检测 - 推理任务', () => {
  const router = require('../model-router');
  assert(router.detectTaskType('分析系统架构设计') === 'reasoning', '架构分析应为 reasoning');
  assert(router.detectTaskType('安全审核代码') === 'reasoning', '安全审核应为 reasoning');
});

test('任务类型检测 - 经济型任务', () => {
  const router = require('../model-router');
  assert(router.detectTaskType('更新 README 文档') === 'economy', 'README 应为 economy');
  assert(router.detectTaskType('添加配置文件注释') === 'economy', '配置应为 economy');
});

test('任务类型检测 - 默认为 coding', () => {
  const router = require('../model-router');
  assert(router.detectTaskType('some unknown task') === 'coding', '未知任务应默认为 coding');
  assert(router.detectTaskType('') === 'general', '空描述应为 general');
});

test('getStatus 未初始化时返回正确状态', () => {
  const router = require('../model-router');
  const status = router.getStatus();
  assert(typeof status === 'object', 'status 应为对象');
  assert('initialized' in status, 'status 应有 initialized 字段');
  assert('models' in status || 'message' in status, 'status 应有 models 或 message 字段');
});

test('step-executor 集成 model-router', () => {
  const seContent = fs.readFileSync(path.join(ROOT, 'glada', 'step-executor.js'), 'utf-8');
  assert(seContent.includes("require('./model-router')"), 'step-executor 应引用 model-router');
  assert(seContent.includes('modelRouter.selectModel'), 'step-executor 应调用 selectModel');
});

test('reflector 集成 model-router', () => {
  const rfContent = fs.readFileSync(path.join(ROOT, 'glada', 'reflector.js'), 'utf-8');
  assert(rfContent.includes("require('./model-router')"), 'reflector 应引用 model-router');
  assert(rfContent.includes('modelRouter.selectModel'), 'reflector 应调用 selectModel');
});

test('service.js 集成 model-router', () => {
  const svcContent = fs.readFileSync(path.join(ROOT, 'glada', 'service.js'), 'utf-8');
  assert(svcContent.includes("require('./model-router')"), 'service.js 应引用 model-router');
  assert(svcContent.includes('/api/glada/models'), 'service.js 应有模型状态端点');
  assert(svcContent.includes('modelRouter.initialize'), 'service.js 应初始化 model-router');
  assert(svcContent.includes('modelRouter.shutdown'), 'service.js 应关闭 model-router');
});

test('ecosystem.config.js 包含模型路由环境变量', () => {
  const ecoContent = fs.readFileSync(path.join(ROOT, 'glada', 'ecosystem.config.js'), 'utf-8');
  assert(ecoContent.includes('GLADA_MODEL_PREFERENCE'), 'ecosystem 应有 GLADA_MODEL_PREFERENCE');
  assert(ecoContent.includes('GLADA_MODEL_REFRESH_MS'), 'ecosystem 应有 GLADA_MODEL_REFRESH_MS');
});

test('install-check.js 包含模型发现检查', () => {
  const icContent = fs.readFileSync(path.join(ROOT, 'glada', 'install-check.js'), 'utf-8');
  assert(icContent.includes('model-router'), 'install-check 应引用 model-router');
  assert(icContent.includes('discoverModels'), 'install-check 应调用 discoverModels');
  assert(icContent.includes('classifyModels'), 'install-check 应调用 classifyModels');
});

test('deploy workflow 包含模型路由配置', () => {
  const wfContent = fs.readFileSync(path.join(ROOT, '.github', 'workflows', 'deploy-to-zhuyuan-server.yml'), 'utf-8');
  assert(wfContent.includes('GLADA_MODEL_PREFERENCE'), 'workflow 应写入 GLADA_MODEL_PREFERENCE');
  assert(wfContent.includes('GLADA_MODEL_REFRESH_MS'), 'workflow 应写入 GLADA_MODEL_REFRESH_MS');
});

// ── 结果 ──

console.log(`\n${'═'.repeat(40)}`);
console.log(`📊 测试结果: ${passed} 通过, ${failed} 失败`);
console.log(`${'═'.repeat(40)}`);

if (failed > 0) {
  process.exit(1);
} else {
  console.log('✅ 全部通过!\n');
}
