'use strict';

const { spawn, execFileSync } = require('child_process');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const { capabilitiesForOllamaModel } = require('./ollama');

/**
 * Ollama Auto-Setup Module
 *
 * Handles one-click Ollama install / pull / configure chain.
 * Every step that installs software requires user confirmation
 * (handled by the UI, not this module).
 */

// ── getInstallCommand ─────────────────────────────────────────────────

function getInstallCommand(platform) {
  if (platform === 'darwin') {
    try {
      execFileSync('which', ['brew'], { stdio: 'pipe' });
      return 'brew install ollama';
    } catch (_) {
      return 'curl -fsSL https://ollama.com/install.sh | sh';
    }
  }
  // linux and everything else
  return 'curl -fsSL https://ollama.com/install.sh | sh';
}

// ── installOllama ─────────────────────────────────────────────────────

async function installOllama(emitProgress) {
  const cmd = getInstallCommand(process.platform);
  if (emitProgress) {
    emitProgress({ step: 'install', status: 'running', message: 'Installing Ollama...' });
  }

  return new Promise((resolve, reject) => {
    const child = spawn('sh', ['-c', cmd], { stdio: 'pipe' });
    let stderr = '';

    child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });

    child.on('close', (code) => {
      if (code === 0) {
        if (emitProgress) {
          emitProgress({ step: 'install', status: 'done', message: 'Ollama installed.' });
        }
        resolve();
      } else {
        const excerpt = stderr.slice(0, 500);
        reject(new Error(`Ollama install failed (exit ${code}): ${excerpt}`));
      }
    });

    child.on('error', (err) => {
      reject(new Error(`Ollama install spawn error: ${err.message}`));
    });
  });
}

// ── startOllamaServer ─────────────────────────────────────────────────

async function startOllamaServer(emitProgress) {
  if (emitProgress) {
    emitProgress({ step: 'start', status: 'running', message: 'Starting Ollama server...' });
  }

  // Enable flash attention on Apple Silicon for ~70% throughput improvement (Ollama 0.21+)
  const env = { ...process.env };
  if (process.platform === 'darwin' && process.arch === 'arm64') {
    env.OLLAMA_FLASH_ATTENTION = env.OLLAMA_FLASH_ATTENTION || '1';
  }

  const child = spawn('ollama', ['serve'], { stdio: 'ignore', detached: true, env });
  child.unref();

  const baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
  const maxAttempts = 30;
  const interval = 500;

  for (let i = 0; i < maxAttempts; i++) {
    try {
      const res = await fetch(`${baseUrl}/api/tags`, { signal: AbortSignal.timeout(2000) });
      if (res.ok) {
        if (emitProgress) {
          emitProgress({ step: 'start', status: 'done', message: 'Ollama server running.' });
        }
        return;
      }
    } catch (_) {
      // not ready yet
    }
    await new Promise((r) => setTimeout(r, interval));
  }

  throw new Error('Ollama server did not start within 15 seconds');
}

// ── pullModel ─────────────────────────────────────────────────────────

async function pullModel(modelName, opts = {}) {
  const baseUrl = opts.baseUrl || process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
  const emitProgress = opts.emitProgress || null;

  const res = await fetch(`${baseUrl}/api/pull`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: modelName, stream: true }),
    signal: AbortSignal.timeout(600000),
  });

  if (!res.ok) {
    const body = await res.text().catch(() => '');
    throw new Error(`Ollama pull failed (${res.status}): ${body.slice(0, 300)}`);
  }

  let lastPct = -1;
  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop(); // keep partial line

    for (const line of lines) {
      if (!line.trim()) continue;
      let json;
      try { json = JSON.parse(line); } catch (_) { continue; }

      if (json.total && json.completed) {
        const pct = Math.round((json.completed / json.total) * 100);
        if (emitProgress && pct - lastPct >= 5) {
          lastPct = pct;
          emitProgress({
            step: 'pull',
            status: 'running',
            message: `Downloading ${modelName}... ${pct}%`,
            progress: pct,
          });
        }
      }

      if (json.status === 'success') {
        if (emitProgress) {
          emitProgress({ step: 'pull', status: 'done', message: `${modelName} downloaded.` });
        }
        return;
      }

      if (json.error) {
        throw new Error(`Ollama pull error: ${json.error}`);
      }
    }
  }

  // If we reach here without a success status, the model was pulled (stream ended normally)
  if (emitProgress) {
    emitProgress({ step: 'pull', status: 'done', message: `${modelName} downloaded.` });
  }
}

// ── registerOllamaProvider ────────────────────────────────────────────

function registerOllamaProvider(brain, modelNames, opts = {}) {
  const baseUrl = opts.baseUrl || process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
  const providerId = opts.providerId || `ollama-${crypto.randomUUID().slice(0, 8)}`;

  brain.upsertModelProvider({
    id: providerId,
    name: 'Ollama (local)',
    type: 'ollama',
    baseUrl,
    apiKeyEncrypted: null,
    enabled: true,
  });

  const modelIds = [];
  for (const modelName of modelNames) {
    const modelId = `${providerId}::${modelName}`;
    brain.upsertModelRegistryEntry({
      id: modelId,
      providerId,
      modelId: modelName,
      displayName: modelName,
      capabilities: capabilitiesForOllamaModel(modelName),
      costPer1mInput: 0,
      costPer1mOutput: 0,
      speedTier: 2,
      enabled: true,
    });
    modelIds.push(modelId);
  }

  // Auto-enable shadow training with the first Ollama model
  try {
    const configPath = path.join(__dirname, '..', 'wall-e-config.json');
    let cfg = {};
    if (fs.existsSync(configPath)) {
      cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    }
    if (!cfg.shadow) {
      // Only auto-enable if shadow was never configured (don't override explicit disable)
      const shadowModel = modelNames[0] || 'gemma4:e4b';
      cfg.shadow = {
        enabled: true,
        sampleRate: 0.3,
        taskTypes: ['chat', 'coding', 'qa', 'slack-reply'],
        maxLatencyMs: 30000,
        models: [{ model: shadowModel, provider: 'ollama', weight: 1.0 }],
      };
      fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2), { mode: 0o600 });
      console.log(`[shadow] Auto-enabled with Ollama model: ${shadowModel}`);
    }
  } catch (err) {
    console.error('[shadow] Failed to auto-enable:', err.message);
  }

  return { providerId, modelIds };
}

module.exports = {
  getInstallCommand,
  installOllama,
  startOllamaServer,
  pullModel,
  registerOllamaProvider,
};
