/**
 * ═══════════════════════════════════════════════════════════
 * 🧠 COS训练触发器 · 端到端训练管线
 * ═══════════════════════════════════════════════════════════
 *
 * 签发: 铸渊 · ICE-GL-ZY001
 * 版权: 国作登字-2026-A-00037559
 *
 * 扫描COS桶中的新语料 → 解压/转换TCS格式 → 启动训练会话
 *
 * 设计:
 *   1. 扫描cold桶，列出所有文件（含非压缩文件和文件夹）
 *   2. 对比tcs-structured/目录，找出未处理的语料
 *   3. 自动提取/转换为TCS结构化格式
 *   4. 启动训练会话，用LLM分析和分类
 *   5. 输出处理结果，写入日志
 *
 * 运行方式:
 *   node scripts/cos-training-trigger.js [scan|extract|train|full]
 *
 *   scan    — 仅扫描，输出未处理语料列表
 *   extract — 扫描并解压/转换为TCS格式
 *   train   — 对已有TCS语料启动训练
 *   full    — 完整流程: 扫描 → 提取 → 训练
 */

'use strict';

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

// ─── 路径 ───
const ROOT = path.resolve(__dirname, '..');
const COS_MODULE = path.join(ROOT, 'server', 'age-os', 'mcp-server', 'cos');
const EXTRACTOR_MODULE = path.join(ROOT, 'server', 'age-os', 'mcp-server', 'tools', 'corpus-extractor-ops');
const TRAINING_MODULE = path.join(ROOT, 'server', 'age-os', 'mcp-server', 'tools', 'training-agent-ops');

// ─── 延迟加载模块（允许在CI中跳过数据库依赖） ───
let cos, extractor, trainer;

function loadModules() {
  cos = require(COS_MODULE);
  extractor = require(EXTRACTOR_MODULE);
  trainer = require(TRAINING_MODULE);
}

// ─── 配置 ───
const DEFAULT_BUCKET = 'cold';
const DEFAULT_PERSONA = 'zhuyuan';
const PROCESSED_PREFIX = 'tcs-structured/';
const MAX_EXTRACT_PER_RUN = 20;  // 每次最多处理文件数
const MAX_TRAIN_PER_RUN = 5;     // 每次最多训练文件数
const MAX_EXTRACT_FILE_SIZE = 200 * 1024 * 1024; // 200MB — 超过此阈值的文件使用分块策略

// ─── 排除路径（不视为语料的目录/文件） ───
const EXCLUDED_PREFIXES = [
  'tcs-structured/',
  'training-sessions/',
  'training-results/',
  'training-memory/',
];

// ─── 支持的语料文件扩展名（含非压缩格式） ───
const CORPUS_EXTENSIONS = [
  '.zip', '.gz', '.tar.gz', '.tgz', '.json.gz',  // 压缩格式
  '.json', '.jsonl', '.md', '.txt', '.csv',        // 非压缩格式
];

/**
 * 判断文件是否为语料文件
 */
function isCorpusFile(key) {
  // 排除处理结果目录
  for (const prefix of EXCLUDED_PREFIXES) {
    if (key.startsWith(prefix)) return false;
  }
  // 匹配扩展名
  const lower = key.toLowerCase();
  return CORPUS_EXTENSIONS.some(ext => lower.endsWith(ext));
}

/**
 * 判断是否为语料目录（如 repo-archive/）
 */
function isCorpusDirectory(key) {
  for (const prefix of EXCLUDED_PREFIXES) {
    if (key.startsWith(prefix)) return false;
  }
  return key.endsWith('/');
}

/**
 * 从已处理列表中判断某文件是否已处理
 */
function isProcessed(rawKey, processedFiles) {
  // 从rawKey提取基础文件名（处理多重扩展名如.tar.gz）
  let baseName = rawKey.split('/').pop();
  // 移除所有已知的语料文件扩展名
  for (const ext of ['.tar.gz', '.json.gz', '.tgz', '.zip', '.gz', '.jsonl', '.json', '.md', '.txt', '.csv']) {
    if (baseName.toLowerCase().endsWith(ext)) {
      baseName = baseName.slice(0, -ext.length);
      break;
    }
  }
  return processedFiles.some(f => f.key.includes(baseName));
}

// ═══════════════════════════════════════════
// 命令: scan — 扫描未处理语料
// ═══════════════════════════════════════════

async function cmdScan(bucket) {
  console.log('═══ COS训练触发器 · 语料扫描 ═══\n');

  const bucketName = bucket || DEFAULT_BUCKET;

  // 列出所有文件
  const allFiles = await cos.list(bucketName, '', 500);
  // 列出已处理文件
  const processed = await cos.list(bucketName, PROCESSED_PREFIX, 500);

  const processedFiles = processed.files.filter(f => f.key.endsWith('.tcs.json'));

  // 分类
  const corpusFiles = [];
  const corpusDirs = [];

  for (const file of allFiles.files) {
    if (isCorpusFile(file.key)) {
      const alreadyProcessed = isProcessed(file.key, processedFiles);
      corpusFiles.push({
        key: file.key,
        size_bytes: file.size_bytes,
        processed: alreadyProcessed
      });
    } else if (isCorpusDirectory(file.key)) {
      corpusDirs.push({ key: file.key });
    }
  }

  const pending = corpusFiles.filter(f => !f.processed);

  console.log(`桶: ${bucketName}`);
  console.log(`总文件数: ${allFiles.files.length}`);
  console.log(`语料文件: ${corpusFiles.length}`);
  console.log(`语料目录: ${corpusDirs.length}`);
  console.log(`已处理: ${corpusFiles.length - pending.length}`);
  console.log(`待处理: ${pending.length}`);
  console.log(`已生成TCS: ${processedFiles.length}`);

  if (pending.length > 0) {
    console.log('\n📋 待处理语料:');
    for (const f of pending) {
      console.log(`  📄 ${f.key} (${formatBytes(f.size_bytes)})`);
    }
  }

  if (corpusDirs.length > 0) {
    console.log('\n📁 语料目录:');
    for (const d of corpusDirs) {
      console.log(`  📂 ${d.key}`);
    }

    // 扫描目录内的文件
    for (const dir of corpusDirs) {
      try {
        const dirFiles = await cos.list(bucketName, dir.key, 100);
        const dirCorpus = dirFiles.files.filter(f => isCorpusFile(f.key));
        if (dirCorpus.length > 0) {
          console.log(`     └── ${dir.key} 内含 ${dirCorpus.length} 个语料文件`);
          for (const f of dirCorpus) {
            const alreadyProcessed = isProcessed(f.key, processedFiles);
            if (!alreadyProcessed) {
              pending.push({
                key: f.key,
                size_bytes: f.size_bytes,
                processed: false
              });
              console.log(`         📄 ${f.key} (${formatBytes(f.size_bytes)}) [待处理]`);
            }
          }
        }
      } catch (err) {
        console.log(`     └── ${dir.key} 扫描失败: ${err.message}`);
      }
    }
  }

  // 写入GitHub Actions输出
  if (process.env.GITHUB_OUTPUT) {
    const outputLines = [
      `pending=${pending.length}`,
      `total_corpus=${corpusFiles.length}`,
      `processed=${processedFiles.length}`,
      `has_new_corpus=${pending.length > 0 ? 'true' : 'false'}`,
      `pending_files=${pending.map(f => f.key).join(',')}`
    ];
    fs.appendFileSync(process.env.GITHUB_OUTPUT, outputLines.join('\n') + '\n');
  }

  return { pending, processedFiles, corpusDirs };
}

// ═══════════════════════════════════════════
// 命令: extract — 提取/转换语料为TCS格式
// ═══════════════════════════════════════════

async function cmdExtract(bucket) {
  console.log('═══ COS训练触发器 · 语料提取 ═══\n');

  const bucketName = bucket || DEFAULT_BUCKET;
  const { pending } = await cmdScan(bucketName);

  if (pending.length === 0) {
    console.log('\n✅ 无待处理语料');
    writeGitHubOutput('extracted=0', 'extract_status=skipped');
    return { extracted: 0, errors: 0 };
  }

  const toProcess = pending.slice(0, MAX_EXTRACT_PER_RUN);
  console.log(`\n🔄 开始提取 ${toProcess.length}/${pending.length} 个文件...\n`);

  let extracted = 0;
  let errors = 0;
  let skipped = 0;
  const results = [];

  for (const file of toProcess) {
    try {
      // 大文件预警
      const sizeMB = (file.size_bytes / 1024 / 1024).toFixed(1);
      if (file.size_bytes > MAX_EXTRACT_FILE_SIZE) {
        console.log(`  📦 处理: ${file.key} (${sizeMB}MB · 超大文件，使用分块策略)...`);
      } else {
        console.log(`  📦 处理: ${file.key} (${sizeMB}MB)...`);
      }

      const result = await extractor.cosExtractCorpus({
        bucket: bucketName,
        key: file.key,
        output_bucket: bucketName,
        output_prefix: PROCESSED_PREFIX
      });

      // 根据返回状态分类计数
      if (result.status === 'zip_detected') {
        skipped++;
        results.push({ key: file.key, status: 'skipped', reason: 'zip_needs_special_tool' });
        console.log(`  ⏭️ 跳过: ${file.key} — ZIP文件需要专用工具处理`);
      } else if (result.status === 'skipped_too_large') {
        skipped++;
        results.push({ key: file.key, status: 'skipped', reason: 'too_large', size_mb: result.size_mb });
        console.log(`  ⏭️ 跳过: ${file.key} — ${result.message}`);
      } else if (result.status === 'partial_extract') {
        extracted++;
        results.push({ key: file.key, status: 'partial', output: result.output?.key, message: result.message });
        console.log(`  🔶 部分提取: ${result.message}`);
      } else {
        extracted++;
        results.push({ key: file.key, status: 'success', output: result.output?.key });
        console.log(`  ✅ 完成: ${result.output?.key || '已处理'} (${result.entries || 0} 条目)`);
      }
    } catch (err) {
      errors++;
      results.push({ key: file.key, status: 'error', error: err.message });
      console.log(`  ❌ 失败: ${file.key} — ${err.message}`);
    }
  }

  console.log(`\n═══ 提取完毕 ═══`);
  console.log(`✅ 成功: ${extracted}`);
  console.log(`⏭️ 跳过: ${skipped}`);
  console.log(`❌ 失败: ${errors}`);
  console.log(`⏳ 剩余: ${pending.length - toProcess.length}`);

  writeGitHubOutput(
    `extracted=${extracted}`,
    `extract_skipped=${skipped}`,
    `extract_errors=${errors}`,
    `extract_status=${errors > 0 ? 'partial' : 'success'}`
  );

  return { extracted, errors, skipped, results };
}

// ═══════════════════════════════════════════
// 命令: train — 对TCS语料启动训练
// ═══════════════════════════════════════════

async function cmdTrain(bucket, personaId) {
  console.log('═══ COS训练触发器 · 训练处理 ═══\n');

  const bucketName = bucket || DEFAULT_BUCKET;
  const persona = personaId || DEFAULT_PERSONA;

  // 列出可用的TCS语料
  const processed = await cos.list(bucketName, PROCESSED_PREFIX, 500);
  const tcsFiles = processed.files.filter(f => f.key.endsWith('.tcs.json'));

  if (tcsFiles.length === 0) {
    console.log('⚠️ 无TCS结构化语料可训练。请先运行 extract 命令。');
    writeGitHubOutput('trained=0', 'train_status=no_corpus');
    return { trained: 0, errors: 0 };
  }

  console.log(`📚 找到 ${tcsFiles.length} 个TCS语料文件`);

  // 检查已有训练结果，避免重复处理
  let existingResults = [];
  try {
    const existing = await cos.list(bucketName, `training-results/${persona}/`, 100);
    existingResults = existing.files.filter(f => f.key.endsWith('.json'));
  } catch (err) {
    console.log(`⚠️ 无法读取已有训练结果: ${err.message}`);
  }

  // 启动训练会话
  console.log(`\n🧠 启动训练会话 · 人格体: ${persona}`);

  let session;
  try {
    session = await trainer.trainingStartSession({
      persona_id: persona,
      corpus_bucket: bucketName,
      corpus_prefix: PROCESSED_PREFIX,
      session_name: `自动训练-${new Date().toISOString().slice(0, 10)}`
    });
    console.log(`✅ 会话已启动: ${session.session_id}`);
    console.log(`   可用模型: ${session.models.available.map(m => m.name).join(', ') || '无'}`);
  } catch (err) {
    console.log(`❌ 训练会话启动失败: ${err.message}`);
    writeGitHubOutput('trained=0', `train_status=session_error`, `train_error=${err.message}`);
    return { trained: 0, errors: 1, error: err.message };
  }

  // 检查是否有可用的LLM模型
  if (!session.models.available || session.models.available.length === 0) {
    console.log('⚠️ 无可用LLM模型（需要配置 ZY_DEEPSEEK_API_KEY 等密钥）');
    console.log('   训练会话已记录，等待LLM密钥配置后再次运行。');
    writeGitHubOutput('trained=0', 'train_status=no_llm_keys');
    return { trained: 0, errors: 0, note: '无LLM密钥' };
  }

  // 处理语料
  const toTrain = tcsFiles.slice(0, MAX_TRAIN_PER_RUN);
  let trained = 0;
  let trainErrors = 0;
  const trainResults = [];

  for (const tcsFile of toTrain) {
    try {
      console.log(`  🔬 训练处理: ${tcsFile.key}...`);
      const result = await trainer.trainingProcessCorpus({
        corpus_bucket: bucketName,
        corpus_key: tcsFile.key,
        persona_id: persona,
        max_entries: 10
      });
      trained++;
      trainResults.push({ key: tcsFile.key, status: 'success', ...result });
      console.log(`  ✅ 完成: ${result.classified}/${result.total} 分类成功`);
    } catch (err) {
      trainErrors++;
      trainResults.push({ key: tcsFile.key, status: 'error', error: err.message });
      console.log(`  ❌ 失败: ${tcsFile.key} — ${err.message}`);
    }
  }

  console.log(`\n═══ 训练完毕 ═══`);
  console.log(`✅ 成功: ${trained}`);
  console.log(`❌ 失败: ${trainErrors}`);

  writeGitHubOutput(
    `trained=${trained}`,
    `train_errors=${trainErrors}`,
    `train_status=${trainErrors > 0 ? 'partial' : 'success'}`
  );

  return { trained, errors: trainErrors, results: trainResults, session_id: session.session_id };
}

// ═══════════════════════════════════════════
// 命令: full — 完整流程
// ═══════════════════════════════════════════

async function cmdFull(bucket, personaId) {
  console.log('╔═══════════════════════════════════════════╗');
  console.log('║  COS训练触发器 · 完整训练管线             ║');
  console.log('║  铸渊 · ICE-GL-ZY001                     ║');
  console.log('╚═══════════════════════════════════════════╝\n');

  const bucketName = bucket || DEFAULT_BUCKET;
  const persona = personaId || DEFAULT_PERSONA;
  const startTime = Date.now();

  // 第一步: 提取语料
  console.log('📍 第一步: 提取语料\n');
  const extractResult = await cmdExtract(bucketName);

  // 第二步: 训练处理
  console.log('\n📍 第二步: 训练处理\n');
  const trainResult = await cmdTrain(bucketName, persona);

  // 汇总
  const duration = Date.now() - startTime;
  console.log('\n╔═══════════════════════════════════════════╗');
  console.log('║  完整训练管线 · 运行完毕                   ║');
  console.log('╚═══════════════════════════════════════════╝');
  console.log(`  提取: ${extractResult.extracted} 成功 / ${extractResult.skipped || 0} 跳过 / ${extractResult.errors} 失败`);
  console.log(`  训练: ${trainResult.trained} 成功 / ${trainResult.errors} 失败`);
  console.log(`  耗时: ${(duration / 1000).toFixed(1)}s`);

  writeGitHubOutput(
    `pipeline_status=${(extractResult.errors + trainResult.errors) > 0 ? 'partial' : 'success'}`,
    `pipeline_duration_ms=${duration}`
  );

  return { extract: extractResult, train: trainResult, duration_ms: duration };
}

// ═══════════════════════════════════════════
// 辅助函数
// ═══════════════════════════════════════════

function formatBytes(bytes) {
  if (bytes < 1024) return `${bytes}B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
}

function writeGitHubOutput(...lines) {
  if (process.env.GITHUB_OUTPUT) {
    fs.appendFileSync(process.env.GITHUB_OUTPUT, lines.join('\n') + '\n');
  }
}

// ═══════════════════════════════════════════
// CLI 入口
// ═══════════════════════════════════════════

async function main() {
  const args = process.argv.slice(2);
  const command = args[0] || 'scan';
  const bucket = args.find(a => a.startsWith('--bucket='))?.split('=')[1] || DEFAULT_BUCKET;
  const persona = args.find(a => a.startsWith('--persona='))?.split('=')[1] || DEFAULT_PERSONA;

  // 加载模块
  try {
    loadModules();
  } catch (err) {
    console.error(`❌ 模块加载失败: ${err.message}`);
    console.error('   请确保 server/age-os/mcp-server/ 依赖已安装');
    process.exit(1);
  }

  switch (command) {
    case 'scan':
      await cmdScan(bucket);
      break;
    case 'extract':
      await cmdExtract(bucket);
      break;
    case 'train':
      await cmdTrain(bucket, persona);
      break;
    case 'full':
      await cmdFull(bucket, persona);
      break;
    default:
      console.log('COS训练触发器 · 铸渊 · ICE-GL-ZY001');
      console.log('');
      console.log('用法:');
      console.log('  node scripts/cos-training-trigger.js scan      — 扫描未处理语料');
      console.log('  node scripts/cos-training-trigger.js extract   — 提取/转换为TCS格式');
      console.log('  node scripts/cos-training-trigger.js train     — 启动训练处理');
      console.log('  node scripts/cos-training-trigger.js full      — 完整流程');
      console.log('');
      console.log('选项:');
      console.log('  --bucket=cold|hot|team   — 指定COS桶（默认cold）');
      console.log('  --persona=zhuyuan        — 指定人格体（默认zhuyuan）');
      break;
  }
}

main().catch(err => {
  console.error('COS训练触发器异常:', err.message);
  if (process.env.GITHUB_OUTPUT) {
    fs.appendFileSync(process.env.GITHUB_OUTPUT, 'pipeline_status=error\n');
    fs.appendFileSync(process.env.GITHUB_OUTPUT, `pipeline_error=${err.message}\n`);
  }
  process.exit(1);
});
