'use strict';

const { execFile } = require('node:child_process');
const { promisify } = require('node:util');

const execFileAsync = promisify(execFile);

// ---------------------------------------------------------------------------
// Built-in benchmark prompts
// ---------------------------------------------------------------------------

const BENCHMARK_PROMPTS = [
  { prompt: 'Explain the difference between a promise and a callback in JavaScript.', type: 'coding' },
  { prompt: 'Summarize the key points of effective code review.', type: 'coding' },
  { prompt: 'What are the pros and cons of microservices vs monolith?', type: 'reasoning' },
  { prompt: 'Write a function to find the longest palindrome in a string.', type: 'coding' },
  { prompt: 'What should I prioritize when debugging a production outage?', type: 'reasoning' },
];

// ---------------------------------------------------------------------------
// Scoring helpers
// ---------------------------------------------------------------------------

/**
 * Score a single response on a 0-1 scale.
 * Factors: non-empty (coherence), length (detail), latency (speed).
 */
function scoreResponse(response, latencyMs) {
  if (!response || !response.trim()) return 0;

  // Coherence: 0.4 points for non-empty
  let score = 0.4;

  // Length score: up to 0.3 points (longer = more detailed, capped at 500 chars)
  const lengthScore = Math.min(response.length / 500, 1) * 0.3;
  score += lengthScore;

  // Latency score: up to 0.3 points (faster = better, under 2s is perfect)
  const latencyScore = Math.max(0, 1 - latencyMs / 10000) * 0.3;
  score += latencyScore;

  return Math.round(score * 1000) / 1000;
}

// ---------------------------------------------------------------------------
// Model runner
// ---------------------------------------------------------------------------

/**
 * Run a prompt against an Ollama model and return { response, latencyMs }.
 * @param {string} model - Ollama model name
 * @param {string} prompt - The prompt text
 * @returns {Promise<{ response: string, latencyMs: number }>}
 */
async function runOllamaPrompt(model, prompt) {
  const start = Date.now();
  try {
    const { stdout } = await execFileAsync('ollama', ['run', model, prompt], {
      timeout: 30000,
      maxBuffer: 1024 * 1024,
    });
    return { response: stdout.trim(), latencyMs: Date.now() - start };
  } catch (err) {
    return { response: '', latencyMs: Date.now() - start, error: err.message };
  }
}

// ---------------------------------------------------------------------------
// Main entry point
// ---------------------------------------------------------------------------

/**
 * Evaluate fine-tuned models against base models using the quorum system.
 * @param {Object} options
 * @param {string[]} options.fineTunedModels - Ollama model names to evaluate
 * @param {string[]} [options.benchmarkPrompts] - Test prompts (defaults to built-in set)
 * @param {string} [options.baseline] - Baseline model name (default: 'llama3.2:1b')
 * @param {number} [options.winThreshold] - Win rate threshold for deployment (default: 0.6)
 * @param {Object} [deps] - { brain, runPrompt }
 * @returns {Promise<{ results: Array, winner: string|null, deployed: boolean }>}
 */
async function evaluateAndDeploy(options, deps = {}) {
  const {
    fineTunedModels = [],
    benchmarkPrompts,
    baseline = 'llama3.2:1b',
    winThreshold = 0.6,
  } = options || {};

  const prompts = benchmarkPrompts || BENCHMARK_PROMPTS;
  const runPrompt = deps.runPrompt || runOllamaPrompt;
  const brain = deps.brain || null;

  if (!fineTunedModels.length) {
    return { results: [], winner: null, deployed: false };
  }

  // All models to evaluate: fine-tuned + baseline
  const allModels = [...fineTunedModels, baseline];
  const results = [];

  // Run each prompt against all models
  for (const { prompt, type } of prompts) {
    const promptResult = { prompt, type, scores: {} };

    for (const model of allModels) {
      try {
        const { response, latencyMs } = await runPrompt(model, prompt);
        const score = scoreResponse(response, latencyMs);
        promptResult.scores[model] = { score, latencyMs, responseLength: (response || '').length };
      } catch (err) {
        promptResult.scores[model] = { score: 0, latencyMs: 0, responseLength: 0, error: err.message };
      }
    }

    results.push(promptResult);
  }

  // Compare each fine-tuned model against baseline
  let winner = null;
  let bestWinRate = 0;
  let bestAvgScore = 0;

  for (const model of fineTunedModels) {
    let wins = 0;
    let comparisons = 0;
    let totalScore = 0;

    for (const r of results) {
      const modelScore = r.scores[model]?.score || 0;
      const baselineScore = r.scores[baseline]?.score || 0;
      comparisons++;
      totalScore += modelScore;
      if (modelScore > baselineScore) wins++;
    }

    const winRate = comparisons > 0 ? wins / comparisons : 0;
    const avgScore = comparisons > 0 ? totalScore / comparisons : 0;

    // Pick model with highest win rate; break ties by average score
    if (winRate > bestWinRate || (winRate === bestWinRate && avgScore > bestAvgScore)) {
      bestWinRate = winRate;
      bestAvgScore = avgScore;
      if (winRate > winThreshold) {
        winner = model;
      }
    }
  }

  // Deploy winner if found
  let deployed = false;
  if (winner) {
    try {
      await deployModel(winner, deps);
      deployed = true;
    } catch (err) {
      // Deployment failed but evaluation still succeeded
      deployed = false;
    }
  }

  return { results, winner, deployed };
}

// ---------------------------------------------------------------------------
// Deployment
// ---------------------------------------------------------------------------

/**
 * Deploy a model to Ollama and register in brain's model_registry.
 * @param {string} modelName - The model to deploy
 * @param {Object} deps - { brain, execDeploy }
 */
async function deployModel(modelName, deps = {}) {
  const execDeploy = deps.execDeploy || execFileAsync;
  const brain = deps.brain || null;

  // Create an Ollama model alias
  const walleAlias = `walle-${modelName.replace(/[/:]/g, '-')}`;

  await execDeploy('ollama', ['cp', modelName, walleAlias], { timeout: 60000 });

  // Register in brain's model_registry if available
  if (brain && brain.getDb) {
    try {
      const db = brain.getDb();
      db.prepare(`
        INSERT OR REPLACE INTO model_registry (name, base_model, status, deployed_at)
        VALUES (?, ?, 'active', datetime('now'))
      `).run(walleAlias, modelName);
    } catch {
      // model_registry table may not exist yet -- non-fatal
    }
  }
}

module.exports = {
  evaluateAndDeploy,
  deployModel,
  scoreResponse,
  BENCHMARK_PROMPTS,
};
