#!/usr/bin/env node
'use strict';

/**
 * Model trainer skill -- runs the full fine-tuning pipeline.
 * Called by skill-executor on schedule.
 */

const path = require('node:path');
const os = require('node:os');
const fs = require('node:fs');

async function run({ brain, log }) {
  const { exportTrainingData } = require(path.resolve(__dirname, '..', '..', '..', 'training', 'exporter'));
  const { trainModel, SUPPORTED_BASES } = require(path.resolve(__dirname, '..', '..', '..', 'training', 'trainer'));
  const { evaluateAndDeploy } = require(path.resolve(__dirname, '..', '..', '..', 'training', 'evaluate'));
  const { execFile } = require('node:child_process');
  const { promisify } = require('node:util');
  const execFileAsync = promisify(execFile);

  // Create unique temp directory for this run
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'walle-training-'));
  const dataPath = path.join(tmpDir, 'training-data.jsonl');
  const modelsDir = path.join(tmpDir, 'models');
  fs.mkdirSync(modelsDir, { recursive: true });

  try {
    // 1. Export training data
    log('Exporting training data...');
    const exportResult = await exportTrainingData({
      brain,
      outputPath: dataPath,
    });

    if (exportResult.stats.total < 100) {
      log(`Only ${exportResult.stats.total} samples -- need at least 100 for fine-tuning. Skipping.`);
      return { skipped: true, reason: 'insufficient_data', samples: exportResult.stats.total };
    }

    log(`Exported ${exportResult.stats.total} training samples.`);

    // 2. Train all base models
    const trainResults = [];
    for (const [baseName] of Object.entries(SUPPORTED_BASES)) {
      log(`Training ${baseName}...`);
      try {
        const result = await trainModel({
          baseModel: baseName,
          dataPath,
          outputDir: modelsDir,
        });
        trainResults.push({ baseName, ...result });
      } catch (e) {
        log(`Training ${baseName} failed: ${e.message}`);
        trainResults.push({ baseName, success: false, error: e.message });
      }
    }

    // 3. Register trained models with Ollama and evaluate
    const ollamaModels = [];
    for (const r of trainResults) {
      if (!r.success || !r.modelPath) continue;
      const modelAlias = `walle-${r.baseName}-v${Date.now()}`;
      try {
        // Create Ollama model from GGUF file
        // Find the .gguf file in the model output directory
        const ggufFiles = fs.readdirSync(r.modelPath).filter(f => f.endsWith('.gguf'));
        if (ggufFiles.length === 0) {
          log(`No GGUF file found in ${r.modelPath}, skipping ${r.baseName}`);
          continue;
        }
        const ggufPath = path.join(r.modelPath, ggufFiles[0]);
        // Create a Modelfile for Ollama
        const modelfilePath = path.join(r.modelPath, 'Modelfile');
        fs.writeFileSync(modelfilePath, `FROM ${ggufPath}\n`);
        await execFileAsync('ollama', ['create', modelAlias, '-f', modelfilePath], { timeout: 120000 });
        ollamaModels.push(modelAlias);
        log(`Registered ${modelAlias} with Ollama`);
      } catch (e) {
        log(`Failed to register ${modelAlias} with Ollama: ${e.message}`);
      }
    }

    if (ollamaModels.length === 0) {
      log('No models could be registered with Ollama.');
      return { skipped: true, reason: 'all_training_failed', trainResults };
    }

    log(`Evaluating ${ollamaModels.length} trained model(s)...`);
    const evalResult = await evaluateAndDeploy(
      { fineTunedModels: ollamaModels },
      { brain }
    );

    if (evalResult.winner) {
      log(`Winner: ${evalResult.winner} (deployed: ${evalResult.deployed})`);
    } else {
      log('No model beat the baseline. No deployment.');
    }

    return { trainResults, evalResult };
  } finally {
    // Cleanup temp directory
    try {
      fs.rmSync(tmpDir, { recursive: true, force: true });
    } catch {
      // Best-effort cleanup
    }
  }
}

module.exports = { run };
