import { spawn } from 'child_process';
import { existsSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { formatOutput } from './output-formatter.js';
import { getNextSteps, analyzeOutputForTips } from './next-steps.js';
import { ConnectionContext } from './connection-context.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

/**
 * Result of command execution
 */
export interface ExecutionResult {
  success: boolean;
  output: string;
  error?: string;
}

/**
 * Enhanced error information with suggestions
 */
interface ErrorAnalysis {
  errorType: string;
  originalError: string;
  possibleCauses: string[];
  suggestions: Array<{
    action: string;
    command?: string;
    parameters?: Record<string, any>;
  }>;
}

/**
 * Analyzes error messages and provides actionable suggestions
 */
function analyzeError(commandName: string, error: string, output: string): ErrorAnalysis {
  const errorLower = error.toLowerCase();
  const outputLower = output.toLowerCase();
  const combined = errorLower + ' ' + outputLower;

  // Table not found errors
  if (combined.includes('table') && (combined.includes('not found') || combined.includes('does not exist') || combined.includes('invalid'))) {
    return {
      errorType: 'TABLE_NOT_FOUND',
      originalError: error,
      possibleCauses: [
        'Table name is case-sensitive - check capitalization',
        'Table may be in a different schema',
        'Table may not exist yet',
        'User may not have permission to see the table',
      ],
      suggestions: [
        {
          action: 'List tables in the schema to verify the table name',
          command: 'hana_tables',
          parameters: { schema: '<schema-name>' },
        },
        {
          action: 'List all available schemas',
          command: 'hana_schemas',
        },
        {
          action: 'Check current user and permissions',
          command: 'hana_status',
        },
      ],
    };
  }

  // Schema not found errors
  if (combined.includes('schema') && (combined.includes('not found') || combined.includes('does not exist') || combined.includes('invalid'))) {
    return {
      errorType: 'SCHEMA_NOT_FOUND',
      originalError: error,
      possibleCauses: [
        'Schema name is case-sensitive',
        'Schema does not exist',
        'User does not have access to the schema',
      ],
      suggestions: [
        {
          action: 'List all available schemas',
          command: 'hana_schemas',
        },
        {
          action: 'Check current user permissions',
          command: 'hana_status',
        },
      ],
    };
  }

  // File not found errors
  if (combined.includes('file') && (combined.includes('not found') || combined.includes('enoent') || combined.includes('cannot find'))) {
    return {
      errorType: 'FILE_NOT_FOUND',
      originalError: error,
      possibleCauses: [
        'File path is incorrect',
        'File does not exist at the specified location',
        'Relative path may need to be absolute',
      ],
      suggestions: [
        {
          action: 'Check that the file exists and path is correct',
        },
        {
          action: 'Use absolute file paths instead of relative paths',
        },
      ],
    };
  }

  // Connection errors
  if (combined.includes('connect') || combined.includes('connection') || combined.includes('econnrefused') || combined.includes('etimedout')) {
    return {
      errorType: 'CONNECTION_ERROR',
      originalError: error,
      possibleCauses: [
        'Database credentials not configured',
        'Database server is not reachable',
        'Network connectivity issue',
        'Database is offline or maintenance',
      ],
      suggestions: [
        {
          action: 'Verify database connection settings in .env or default-env.json',
        },
        {
          action: 'Check if database server is running',
        },
        {
          action: 'Test basic connectivity',
          command: 'hana_status',
        },
      ],
    };
  }

  // Authentication errors
  if (combined.includes('authenticat') || combined.includes('authorization') || combined.includes('credential') || combined.includes('permission denied')) {
    return {
      errorType: 'AUTHENTICATION_ERROR',
      originalError: error,
      possibleCauses: [
        'Invalid username or password',
        'User account may be locked or expired',
        'Insufficient privileges for the operation',
      ],
      suggestions: [
        {
          action: 'Verify credentials in .env or default-env.json',
        },
        {
          action: 'Check user status and roles',
          command: 'hana_status',
        },
        {
          action: 'Contact database administrator for access',
        },
      ],
    };
  }

  // Timeout errors
  if (combined.includes('timeout') || combined.includes('timed out')) {
    return {
      errorType: 'TIMEOUT',
      originalError: error,
      possibleCauses: [
        'Operation taking too long (default 30s)',
        'Large dataset requires more time',
        'Database performance issue',
      ],
      suggestions: [
        {
          action: 'For data operations, consider filtering or limiting results',
        },
        {
          action: 'Check system health',
          command: 'hana_healthCheck',
        },
        {
          action: 'For import/export, use timeoutSeconds parameter to increase timeout',
        },
      ],
    };
  }

  // Parameter/syntax errors
  if (combined.includes('parameter') || combined.includes('argument') || combined.includes('required') || combined.includes('missing')) {
    return {
      errorType: 'PARAMETER_ERROR',
      originalError: error,
      possibleCauses: [
        'Required parameter is missing',
        'Parameter value format is incorrect',
        'Parameter name may be misspelled',
      ],
      suggestions: [
        {
          action: 'Check parameter requirements and examples',
          command: 'hana_examples',
          parameters: { command: commandName },
        },
        {
          action: 'View parameter presets for this command',
          command: 'hana_parameter_presets',
          parameters: { command: commandName },
        },
      ],
    };
  }

  // Generic error with generic suggestions
  return {
    errorType: 'UNKNOWN_ERROR',
    originalError: error,
    possibleCauses: [
      'Check the error message for specific details',
    ],
    suggestions: [
      {
        action: 'Try checking system health',
        command: 'hana_healthCheck',
      },
      {
        action: 'View examples for this command',
        command: 'hana_examples',
        parameters: { command: commandName },
      },
    ],
  };
}

/**
 * Executes a hana-cli command and captures its output
 * 
 * @param commandName - The command to execute (e.g., 'status', 'tables')
 * @param args - Arguments to pass to the command as key-value pairs
 * @param context - Optional connection context for project-specific connections
 * @returns Promise with execution result including the command name for formatting
 */
export async function executeCommand(
  commandName: string,
  args: Record<string, any> = {},
  context?: ConnectionContext
): Promise<ExecutionResult & { commandName: string }> {
  return new Promise((resolve) => {
    try {
      // Build the CLI path - go up from build/ to project root, then to bin/cli.js
      const cliPath = join(__dirname, '..', '..', 'bin', 'cli.js');
      
      // Convert args object to command line arguments
      const commandArgs: string[] = [commandName];
      
      for (const [key, value] of Object.entries(args)) {
        if (value === undefined || value === null) continue;
        
        // Handle boolean flags
        if (typeof value === 'boolean') {
          if (value) {
            commandArgs.push(`--${key}`);
          }
          continue;
        }
        
        // Handle arrays
        if (Array.isArray(value)) {
          value.forEach(v => {
            commandArgs.push(`--${key}`, String(v));
          });
          continue;
        }
        
        // Handle other values
        commandArgs.push(`--${key}`, String(value));
      }

      let stdout = '';
      let stderr = '';

      // Build environment with connection context
      const env: Record<string, string> = {
        ...process.env,
        // Ensure stdio output is captured
        FORCE_COLOR: '0',
      };

      // Apply project context to environment
      if (context?.projectPath) {
        env.HANA_CLI_PROJECT_PATH = context.projectPath;
      }

      if (context?.connectionFile) {
        env.HANA_CLI_CONN_FILE = context.connectionFile;
      }

      // Set direct credentials if provided (use cautiously for security)
      if (context?.host) {
        env.HANA_CLI_HOST = context.host;
        env.HANA_CLI_PORT = String(context.port || 30013);
        if (context.user) {
          env.HANA_CLI_USER = context.user;
        }
        if (context.password) {
          env.HANA_CLI_PASSWORD = context.password;
        }
        if (context.database) {
          env.HANA_CLI_DATABASE = context.database;
        }
      }

      // Determine working directory based on context
      let cwd: string;
      if (context?.projectPath) {
        cwd = context.projectPath;
      } else {
        // Prefer the process working directory (where MCP server was launched)
        // over the hana-cli install directory, since agents typically launch
        // the MCP server from within the target project
        const launchDir = process.cwd();
        const hasProjectMarker = ['default-env.json', '.env', 'package.json', '.cdsrc-private.json']
          .some(f => existsSync(join(launchDir, f)));
        cwd = hasProjectMarker ? launchDir : join(__dirname, '..', '..');
      }

      // Spawn the CLI process
      const child = spawn('node', [cliPath, ...commandArgs], {
        env,
        cwd,
      });

      // Capture stdout
      child.stdout.on('data', (data) => {
        stdout += data.toString();
      });

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

      // Handle process completion
      child.on('close', (code) => {
        if (code === 0) {
          resolve({
            success: true,
            output: stdout || 'Command completed successfully',
            commandName,
          });
        } else {
          resolve({
            success: false,
            output: stdout,
            error: stderr || `Command exited with code ${code}`,
            commandName,
          });
        }
      });

      // Handle process errors
      child.on('error', (error) => {
        resolve({
          success: false,
          output: '',
          error: `Failed to execute command: ${error.message}`,
          commandName,
        });
      });

      // Set a timeout to prevent hanging
      const timeout = setTimeout(() => {
        child.kill();
        resolve({
          success: false,
          output: stdout,
          error: 'Command execution timeout (30s)',
          commandName,
        });
      }, 30000);

      child.on('close', () => {
        clearTimeout(timeout);
      });

    } catch (error) {
      resolve({
        success: false,
        output: '',
        error: `Execution error: ${error instanceof Error ? error.message : String(error)}`,
        commandName,
      });
    }
  });
}

/**
 * Formats execution result for display using the output formatter
 */
export function formatResult(result: ExecutionResult & { commandName: string }): string {
  if (result.success) {
    // Apply the formatter to the output
    let formattedOutput = formatOutput(result.commandName, result.output);
    
    // Add context-aware tips based on output analysis
    const tips = analyzeOutputForTips(result.commandName, result.output);
    if (tips.length > 0) {
      formattedOutput += '\n\n**📌 Tips:**\n' + tips.join('\n');
    }
    
    // Add suggested next steps
    const nextSteps = getNextSteps(result.commandName, result.output);
    if (nextSteps.length > 0) {
      formattedOutput += '\n\n**🔄 Suggested Next Steps:**\n';
      nextSteps.forEach((step, i) => {
        formattedOutput += `${i + 1}. **${step.description}**\n`;
        if (step.reason) {
          formattedOutput += `   ${step.reason}\n`;
        }
        if (step.parameters) {
          const paramStr = Object.entries(step.parameters)
            .map(([k, v]) => `${k}: "${v}"`)
            .join(', ');
          formattedOutput += `   → Use: \`hana_${step.command}\` with { ${paramStr} }\n`;
        } else {
          formattedOutput += `   → Use: \`hana_${step.command}\`\n`;
        }
      });
    }
    
    return formattedOutput;
  } else {
    // Analyze the error and provide helpful suggestions
    const errorAnalysis = analyzeError(result.commandName, result.error || '', result.output);
    
    const parts = [];
    
    // Add the error header
    parts.push('❌ **Command Failed**\n');
    
    // Add original error
    parts.push('**Error:**');
    parts.push(errorAnalysis.originalError);
    
    // Add output if available
    if (result.output && result.output.trim()) {
      parts.push('\n**Output:**');
      parts.push(result.output);
    }
    
    // Add possible causes
    if (errorAnalysis.possibleCauses.length > 0) {
      parts.push('\n**Possible Causes:**');
      errorAnalysis.possibleCauses.forEach((cause, i) => {
        parts.push(`${i + 1}. ${cause}`);
      });
    }
    
    // Add actionable suggestions
    if (errorAnalysis.suggestions.length > 0) {
      parts.push('\n**💡 Suggestions:**');
      errorAnalysis.suggestions.forEach((suggestion, i) => {
        parts.push(`${i + 1}. ${suggestion.action}`);
        if (suggestion.command) {
          if (suggestion.parameters) {
            const paramStr = Object.entries(suggestion.parameters)
              .map(([k, v]) => `${k}: "${v}"`)
              .join(', ');
            parts.push(`   → Try: \`${suggestion.command}\` with parameters: { ${paramStr} }`);
          } else {
            parts.push(`   → Try: \`${suggestion.command}\``);
          }
        }
      });
    }
    
    return parts.join('\n');
  }
}
