/**
 * Command Verifier Execution
 *
 * Executes shell commands and extracts results.
 */

import { execa, type ExecaError } from 'execa';
import type { VerifierConfig, VerifierResult, VerificationContext } from './types.js';
import { extractIssues, truncateOutput } from './issues.js';
import { createLogger, type Logger } from '../utils/logger.js';

/**
 * Options for command execution
 */
export interface CommandVerifierOptions {
  /** Logger instance */
  logger?: Logger;
}

/**
 * Run a command-based verifier
 */
export async function runCommandVerifier(
  config: VerifierConfig,
  context: VerificationContext,
  options?: CommandVerifierOptions
): Promise<VerifierResult> {
  const logger = options?.logger ?? createLogger({ name: 'command-verifier' });
  const startTime = Date.now();

  if (!config.command) {
    return {
      name: config.name,
      passed: false,
      output: '',
      duration: 0,
      issues: [],
      error: 'No command specified for command verifier',
    };
  }

  logger.debug({ name: config.name, command: config.command }, 'Running command verifier');

  try {
    // Execute the command
    const result = await execa(config.command, {
      shell: true,
      cwd: context.cwd ?? process.cwd(),
      timeout: config.timeout * 1000,
      reject: false, // Don't throw on non-zero exit
      env: {
        ...process.env,
        // Pass context as environment variables
        EMDASH_GOAL: context.goal ?? '',
        EMDASH_FILES_CHANGED: context.filesChanged?.join(',') ?? '',
      },
    });

    const duration = (Date.now() - startTime) / 1000;
    const output = truncateOutput(
      [result.stdout, result.stderr].filter(Boolean).join('\n')
    );

    // Determine pass/fail
    const passed = config.passOnExit0 ? result.exitCode === 0 : true;

    // Extract issues from output if failed
    const issues = passed ? [] : extractIssues(output);

    logger.debug(
      { name: config.name, passed, exitCode: result.exitCode, duration },
      'Command verifier completed'
    );

    return {
      name: config.name,
      passed,
      output,
      duration,
      issues,
    };
  } catch (error) {
    const duration = (Date.now() - startTime) / 1000;

    // Handle timeout
    if ((error as ExecaError).timedOut) {
      logger.warn({ name: config.name, timeout: config.timeout }, 'Command verifier timed out');
      return {
        name: config.name,
        passed: false,
        output: '',
        duration,
        issues: [{ message: `Command timed out after ${config.timeout}s`, severity: 'error' }],
        error: 'Command timed out',
      };
    }

    // Handle other errors
    const execaError = error as ExecaError;
    const output = truncateOutput(
      [execaError.stdout, execaError.stderr].filter(Boolean).join('\n')
    );

    logger.error({ name: config.name, error }, 'Command verifier failed');

    return {
      name: config.name,
      passed: false,
      output,
      duration,
      issues: extractIssues(output),
      error: execaError.message,
    };
  }
}

/**
 * Check if a command exists
 */
export async function commandExists(command: string): Promise<boolean> {
  try {
    // Extract the base command (first word)
    const baseCommand = command.split(/\s+/)[0];
    await execa('which', [baseCommand]);
    return true;
  } catch {
    return false;
  }
}
