/**
 * Shell Command Tool
 * Executes shell commands with proper error handling and output formatting
 */

const { exec, spawn } = require('child_process');
const { promisify } = require('util');
const { BaseTool, ToolResult } = require('./tool-base.cjs');

const execAsync = promisify(exec);

class ShellTool extends BaseTool {
  constructor(config = {}) {
    super({
      name: 'shell',
      description: 'Execute shell commands in the terminal',
      parameters: [
        {
          name: 'command',
          type: 'string',
          required: true,
          description: 'The shell command to execute'
        },
        {
          name: 'cwd',
          type: 'string',
          required: false,
          description: 'Working directory for command execution'
        },
        {
          name: 'timeout',
          type: 'number',
          required: false,
          description: 'Execution timeout in milliseconds (default: 30000)'
        },
        {
          name: 'env',
          type: 'object',
          required: false,
          description: 'Environment variables for the command'
        }
      ],
      ...config
    });

    this.defaultTimeout = config.defaultTimeout || 30000;
    this.maxOutputSize = config.maxOutputSize || 100000; // 100KB
  }

  /**
   * Execute a shell command
   */
  async execute(params) {
    const { command, cwd, timeout = this.defaultTimeout, env } = params;

    try {
      // Security check for dangerous commands
      const securityCheck = this.checkSecurity(command);
      if (!securityCheck.safe) {
        throw new Error(`Security warning: ${securityCheck.reason}`);
      }

      const options = {
        timeout,
        cwd: cwd || process.cwd(),
        env: { ...process.env, ...env },
        maxBuffer: 10 * 1024 * 1024 // 10MB
      };

      // Execute command
      const { stdout, stderr } = await execAsync(command, options);

      // Format output
      const output = this.formatOutput(stdout, stderr);

      return ToolResult.success(
        { stdout, stderr, exitCode: 0 },
        output,
        { command, cwd: options.cwd }
      );
    } catch (error) {
      // Handle execution errors
      const output = this.formatErrorOutput(error);

      return ToolResult.failure(
        error,
        output,
        { command, exitCode: error.code || 1 }
      );
    }
  }

  /**
   * Basic security check for commands
   */
  checkSecurity(command) {
    const dangerousPatterns = [
      'rm -rf /',
      'rm -rf /*',
      'mkfs',
      'format',
      '> /dev/sd',
      'dd if=',
      ':(){:|:&};:', // Fork bomb
      'chmod 000 /',
      'chown -R'
    ];

    const lowerCommand = command.toLowerCase();

    for (const pattern of dangerousPatterns) {
      if (lowerCommand.includes(pattern)) {
        return {
          safe: false,
          reason: `Command contains dangerous pattern: ${pattern}`
        };
      }
    }

    return { safe: true };
  }

  /**
   * Format command output for display
   */
  formatOutput(stdout, stderr) {
    let output = '';

    if (stdout && stdout.trim()) {
      output += stdout;
    }

    if (stderr && stderr.trim()) {
      if (output) output += '\n';
      output += `[stderr]: ${stderr}`;
    }

    // Truncate if too large
    if (output.length > this.maxOutputSize) {
      output = output.substring(0, this.maxOutputSize);
      output += `\n... [Output truncated, exceeded ${this.maxOutputSize} bytes]`;
    }

    return output || '[No output]';
  }

  /**
   * Format error output
   */
  formatErrorOutput(error) {
    let output = '';

    if (error.killed) {
      output = `Command timed out after ${error.timeout}ms`;
    } else if (error.code) {
      output = `Command failed with exit code ${error.code}`;
    } else {
      output = `Command failed: ${error.message}`;
    }

    if (error.stderr) {
      output += `\n${error.stderr}`;
    }

    if (error.stdout) {
      output += `\n${error.stdout}`;
    }

    return output;
  }
}

/**
 * Streaming Shell Tool
 * For long-running commands with real-time output
 */
class StreamingShellTool extends BaseTool {
  constructor(config = {}) {
    super({
      name: 'shell_stream',
      description: 'Execute long-running shell commands with streaming output',
      parameters: [
        {
          name: 'command',
          type: 'string',
          required: true,
          description: 'The shell command to execute'
        },
        {
          name: 'cwd',
          type: 'string',
          required: false,
          description: 'Working directory for command execution'
        },
        {
          name: 'onData',
          type: 'function',
          required: false,
          description: 'Callback for streaming data chunks'
        }
      ],
      ...config
    });
  }

  async execute(params) {
    return new Promise((resolve, reject) => {
      const { command, cwd, onData } = params;
      const output = { stdout: '', stderr: '' };

      const options = {
        cwd: cwd || process.cwd(),
        shell: true
      };

      const proc = spawn(command, options);

      proc.stdout.on('data', (data) => {
        const text = data.toString();
        output.stdout += text;
        if (onData) onData({ type: 'stdout', data: text });
      });

      proc.stderr.on('data', (data) => {
        const text = data.toString();
        output.stderr += text;
        if (onData) onData({ type: 'stderr', data: text });
      });

      proc.on('close', (code) => {
        if (code === 0) {
          resolve(
            ToolResult.success(
              output,
              output.stdout || '[Process completed successfully]',
              { exitCode: code }
            )
          );
        } else {
          resolve(
            ToolResult.failure(
              new Error(`Process exited with code ${code}`),
              output.stderr || output.stdout,
              { exitCode: code, ...output }
            )
          );
        }
      });

      proc.on('error', (error) => {
        resolve(
          ToolResult.failure(
            error,
            `Failed to spawn process: ${error.message}`,
            { error: error.message }
          )
        );
      });
    });
  }
}

module.exports = {
  ShellTool,
  StreamingShellTool
};
