'use strict';

const { createClient } = require('../llm/client');
const { classifyTaskType } = require('./harvester');
const fs = require('fs');
const path = require('path');

/**
 * Read shadow config from wall-e-config.json.
 */
function getShadowConfig() {
  try {
    const cfgPath = path.join(__dirname, '..', 'wall-e-config.json');
    const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
    return cfg.shadow || { enabled: false };
  } catch {
    return { enabled: false };
  }
}

/**
 * Run a shadow model in parallel on the same prompt.
 * Fire-and-forget — never blocks the primary response.
 *
 * @param {Object} opts
 * @param {string} opts.message - The user's message
 * @param {Object} opts.primaryResponse - { content, model, latencyMs }
 * @param {string} opts.sessionId - Chat session ID
 * @param {Object} opts.brain - Brain module for DB access
 * @param {Object} [opts.config] - Override shadow config (for testing)
 * @param {Function} [opts.clientFactory] - Override client creation (for testing)
 */
async function runShadow({ message, primaryResponse, sessionId, brain, config, clientFactory, taskTypeOverride }) {
  const shadowConfig = config || getShadowConfig();
  if (!shadowConfig.enabled) return null;

  // Sample rate check
  const sampleRate = shadowConfig.sampleRate ?? 0.3;
  if (Math.random() > sampleRate) return null;

  // Task type filtering
  const taskType = taskTypeOverride || classifyTaskType(message);
  const allowedTypes = shadowConfig.taskTypes || ['chat', 'coding', 'qa', 'slack-reply'];
  if (!taskTypeOverride && !allowedTypes.includes(taskType)) return null;

  // Pick a shadow model (weighted random if multiple)
  const models = shadowConfig.models || [];
  if (models.length === 0) return null;
  const shadowModel = pickWeightedModel(models);

  // Shadow is fire-and-forget — use generous timeout (10 min safety ceiling).
  // Local Ollama models can take 30-120s+ on complex coding prompts.
  const maxLatencyMs = shadowConfig.maxLatencyMs || 600000;
  const factory = clientFactory || createClient;
  const client = factory(shadowModel.provider || 'ollama', {
    apiKey: shadowModel.apiKey,
    baseUrl: shadowModel.baseUrl,
  });

  const start = Date.now();
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), maxLatencyMs);

  try {
    const response = await client.chat({
      model: shadowModel.model,
      messages: [{ role: 'user', content: message }],
      maxTokens: 4096,
      signal: controller.signal,
    });
    clearTimeout(timeout);

    const shadowLatencyMs = Date.now() - start;
    const shadowContent = typeof response.content === 'string'
      ? response.content
      : (response.text || JSON.stringify(response.content));

    const id = brain.insertShadowResult({
      sessionId,
      taskType,
      prompt: message,
      primaryResponse: primaryResponse.content,
      primaryModel: primaryResponse.model,
      primaryLatencyMs: primaryResponse.latencyMs,
      shadowResponse: shadowContent,
      shadowModel: shadowModel.model,
      shadowLatencyMs,
    });

    return { id, shadowModel: shadowModel.model, latencyMs: shadowLatencyMs };
  } catch (err) {
    clearTimeout(timeout);

    brain.insertShadowResult({
      sessionId,
      taskType,
      prompt: message,
      primaryResponse: primaryResponse.content,
      primaryModel: primaryResponse.model,
      primaryLatencyMs: primaryResponse.latencyMs,
      shadowResponse: null,
      shadowModel: shadowModel.model,
      shadowLatencyMs: Date.now() - start,
      error: err.message,
    });

    return null;
  }
}

/**
 * Pick a model from the list using weighted random selection.
 */
function pickWeightedModel(models) {
  if (models.length === 1) return models[0];
  const totalWeight = models.reduce((sum, m) => sum + (m.weight || 1), 0);
  let rand = Math.random() * totalWeight;
  for (const m of models) {
    rand -= (m.weight || 1);
    if (rand <= 0) return m;
  }
  return models[0];
}

/**
 * Shadow wrapper for coding orchestrator tasks.
 * Fire-and-forget — safe to call without await.
 */
async function runCodingShadow({ prompt, primaryResponse, taskType, brain }) {
  return runShadow({
    message: prompt,
    primaryResponse: { content: primaryResponse, model: 'coding-orchestrator', latencyMs: 0 },
    sessionId: null,
    brain,
    taskTypeOverride: taskType,
  });
}

module.exports = {
  runShadow,
  runCodingShadow,
  getShadowConfig,
  pickWeightedModel,
};
