/**
 * Shared utilities for agent command handlers.
 * Used by both CLI relay clients and backend server.
 */
import { spawn } from 'child_process';
import { promises as fsPromises } from 'fs';
import os from 'os';
import path from 'path';

/**
 * Execute a shell command and return stdout.
 * @param {string} cmd - Command to run
 * @param {string[]} args - Command arguments
 * @param {object} options - { cwd, env, timeout }
 * @returns {Promise<string>} stdout
 */
export function execCommand(cmd, args, options = {}) {
  return new Promise((resolve, reject) => {
    const proc = spawn(cmd, args, {
      shell: true,
      cwd: options.cwd || os.homedir(),
      env: { ...process.env, ...options.env },
      stdio: ['pipe', 'pipe', 'pipe'],
    });

    let stdout = '';
    let stderr = '';
    proc.stdout.on('data', (d) => { stdout += d; });
    proc.stderr.on('data', (d) => { stderr += d; });

    const timeout = setTimeout(() => {
      proc.kill();
      reject(new Error('Command timed out'));
    }, options.timeout || 30000);

    proc.on('close', (code) => {
      clearTimeout(timeout);
      if (code === 0) resolve(stdout);
      else reject(new Error(stderr || `Exit code ${code}`));
    });

    proc.on('error', (err) => {
      clearTimeout(timeout);
      reject(err);
    });
  });
}

/**
 * Build a file tree for a directory (recursive).
 * @param {string} dirPath - Directory to scan
 * @param {number} maxDepth - Maximum recursion depth
 * @param {number} currentDepth - Current depth (internal)
 * @param {object} options - { maxEntries, includeStats, skipDotfilesAtRoot }
 * @returns {Promise<Array>} File tree items
 */
export async function buildFileTree(dirPath, maxDepth, currentDepth = 0, options = {}) {
  const { maxEntries = 200, includeStats = false, skipDotfilesAtRoot = false } = options;
  if (currentDepth >= maxDepth) return [];

  const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.svn', '.hg']);

  try {
    const entries = await fsPromises.readdir(dirPath, { withFileTypes: true });
    const items = [];
    for (const entry of entries.slice(0, maxEntries)) {
      if (SKIP_DIRS.has(entry.name)) continue;
      if (entry.name.startsWith('.') && skipDotfilesAtRoot && currentDepth === 0) continue;

      const itemPath = path.join(dirPath, entry.name);
      const item = {
        name: entry.name,
        path: itemPath,
        type: entry.isDirectory() ? 'directory' : 'file',
      };

      if (includeStats) {
        try {
          const stats = await fsPromises.stat(itemPath);
          item.size = stats.size;
          item.modified = stats.mtime.toISOString();
        } catch { /* ignore stat errors */ }
      }

      if (entry.isDirectory() && currentDepth < maxDepth - 1) {
        item.children = await buildFileTree(itemPath, maxDepth, currentDepth + 1, options);
      }
      items.push(item);
    }
    return items;
  } catch {
    return [];
  }
}

/**
 * Resolve a path that may start with ~ to an absolute path.
 * @param {string} inputPath - Path to resolve
 * @returns {string} Resolved absolute path
 */
export function resolveTildePath(inputPath) {
  if (!inputPath) return os.homedir();
  if (inputPath === '~') return os.homedir();
  if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
    return path.join(os.homedir(), inputPath.slice(2));
  }
  return path.resolve(inputPath);
}

/** Blocked paths for file reads (security) */
export const BLOCKED_READ_PATTERNS = ['/etc/shadow', '/etc/passwd', '.ssh/id_rsa', '.ssh/id_ed25519', '/.env'];

/** Blocked paths for file writes (security) */
export const BLOCKED_WRITE_PATTERNS = [
  '/etc/', '/usr/bin/', '/usr/sbin/',
  '/windows/system32', '/windows/syswow64', '/program files',
  '.ssh/', '/.env',
];

/** Dangerous shell patterns to block */
export const DANGEROUS_SHELL_PATTERNS = [
  'rm -rf /', 'mkfs', 'dd if=', ':(){', 'fork bomb', '> /dev/sd',
  'format c:', 'format d:', 'format e:', 'del /s /q c:\\',
  'rd /s /q c:\\', 'reg delete', 'bcdedit',
];

/**
 * Check if a normalized path matches any blocked patterns.
 * @param {string} normalizedPath - Absolute path (resolved)
 * @param {string[]} patterns - Blocked patterns to check
 * @returns {boolean}
 */
export function isBlockedPath(normalizedPath, patterns) {
  const lower = normalizedPath.toLowerCase().replace(/\\/g, '/');
  return patterns.some(b => lower.includes(b));
}
