'use strict';
const { execFileSync } = require('child_process');
const path = require('path');
let _recoveryLogger;
try { _recoveryLogger = require('../lib/recovery-logger'); } catch { _recoveryLogger = null; }

/**
 * Validate a skill's `requires` field before execution.
 * Checks: bins (command availability), env (environment variables), mcp (MCP server configs).
 * Returns { valid: boolean, missing: [{ type, name, suggestion }] }
 */
function validateSkillRequirements(skill) {
  const requires = skill.requires || {};
  const missing = [];

  // Check required binaries
  if (requires.bins) {
    const bins = Array.isArray(requires.bins) ? requires.bins : [requires.bins];
    for (const bin of bins) {
      try {
        execFileSync('which', [bin], { encoding: 'utf8', stdio: 'pipe' });
      } catch {
        missing.push({ type: 'bin', name: bin, suggestion: `Install ${bin} or add it to PATH` });
      }
    }
  }

  // Check required environment variables
  if (requires.env) {
    const envVars = Array.isArray(requires.env) ? requires.env : [requires.env];
    for (const envVar of envVars) {
      if (!process.env[envVar]) {
        missing.push({ type: 'env', name: envVar, suggestion: `Set ${envVar} in .env or environment` });
      }
    }
  }

  // Check required MCP servers
  if (requires.mcp) {
    const mcpServers = Array.isArray(requires.mcp) ? requires.mcp : [requires.mcp];
    const availableMcp = getAvailableMcpServers();
    for (const server of mcpServers) {
      if (!availableMcp.has(server)) {
        missing.push({ type: 'mcp', name: server, suggestion: `Configure MCP server "${server}" in Claude Code settings` });
      }
    }
  }

  return { valid: missing.length === 0, missing };
}

/**
 * Get set of available MCP server names from Claude Code config.
 */
function getAvailableMcpServers() {
  const servers = new Set();
  try {
    const fs = require('fs');
    const configPaths = [
      path.join(process.env.HOME || '', '.claude', 'claude_desktop_config.json'),
      path.join(process.env.HOME || '', '.config', 'claude', 'claude_desktop_config.json'),
    ];
    for (const cp of configPaths) {
      try {
        const config = JSON.parse(fs.readFileSync(cp, 'utf8'));
        if (config.mcpServers) {
          Object.keys(config.mcpServers).forEach(k => servers.add(k));
        }
      } catch {}
    }
  } catch {}
  return servers;
}

/**
 * Item G of error-recovery deep-dive — runtime tool degradation check.
 *
 * Borrowed concept from OpenClaw's exec-runtime fallback (pty fallback,
 * systemd-unavailable detection). Wall-E's adaptation: when a skill is
 * about to invoke a binary that DID pass preflight but might have
 * disappeared mid-run (PATH change, brew uninstall, package manager
 * cleanup), check ONE more time at the actual call site and degrade
 * gracefully instead of crashing.
 *
 * Returns { ok: boolean, missing?: string, suggestion?: string }.
 *
 * Logs via recovery-logger so disappeared-mid-run can be diagnosed
 * without console.log spelunking.
 */
function checkBinAvailable(binName, opts = {}) {
  if (!binName || typeof binName !== 'string') {
    return { ok: false, missing: '<empty>', suggestion: 'binName is required' };
  }
  try {
    execFileSync('which', [binName], { encoding: 'utf8', stdio: 'pipe' });
    return { ok: true };
  } catch {
    const result = {
      ok: false,
      missing: binName,
      suggestion: `Binary "${binName}" not on PATH at call time. Was it preflight-validated then removed/uninstalled?`,
    };
    if (_recoveryLogger) {
      try {
        _recoveryLogger.warn('tool-degradation', 'binary_missing_at_runtime', {
          skill: opts.skill || null,
          details: {
            bin: binName,
            context: opts.context || 'unspecified',
          },
        });
      } catch {}
    }
    return result;
  }
}

/**
 * Run a binary with graceful degradation. Combines availability check +
 * exec into one helper that callers can use as a drop-in replacement
 * for execFileSync where appropriate.
 *
 * Returns { ok, stdout?, stderr?, status?, missing?, error? }.
 *
 * On binary-missing: returns ok=false WITHOUT throwing. Caller decides
 * whether to surface a service alert, skip the operation, or fall back
 * to a different tool.
 *
 * On exec failure (non-zero exit): returns ok=false with stderr/status
 * set; caller can decide how to react.
 */
function runWithDegradation(binName, args = [], opts = {}) {
  const check = checkBinAvailable(binName, opts);
  if (!check.ok) {
    return Object.assign({ ok: false }, check);
  }
  try {
    const stdout = execFileSync(binName, args, {
      encoding: opts.encoding || 'utf8',
      stdio: opts.stdio || ['ignore', 'pipe', 'pipe'],
      timeout: opts.timeout || 30_000,
      env: opts.env || process.env,
    });
    return { ok: true, stdout };
  } catch (err) {
    if (_recoveryLogger) {
      try {
        _recoveryLogger.warn('tool-degradation', 'binary_exec_failure', {
          skill: opts.skill || null,
          details: {
            bin: binName,
            args: args.slice(0, 10),
            status: err.status,
            stderr: (err.stderr || '').toString().slice(0, 500),
            error: (err.message || '').slice(0, 200),
            context: opts.context || 'unspecified',
          },
        });
      } catch {}
    }
    return {
      ok: false,
      missing: null,
      error: err.message,
      status: err.status,
      stderr: err.stderr ? err.stderr.toString() : null,
    };
  }
}

module.exports = {
  validateSkillRequirements,
  checkBinAvailable,
  runWithDegradation,
};
