/**
 * Parallel Execution System for Generators
 * Executes independent generators in parallel for significant performance improvements
 */

import * as os from 'os';
import { GeneratorContext, Generator, GenerationResult, GeneratedFile } from './types';

export interface ParallelExecutionOptions {
  /**
   * Maximum number of generators to run in parallel
   * Default: Number of CPU cores - 1, min 2, max 8
   */
  maxConcurrency?: number;

  /**
   * Timeout for each generator in milliseconds
   * Default: 60000 (60 seconds)
   */
  timeout?: number;

  /**
   * Whether to fail fast (stop on first error) or collect all errors
   * Default: false (collect all errors)
   */
  failFast?: boolean;

  /**
   * Progress callback
   */
  onProgress?: (completed: number, total: number, generatorName: string) => void;
}

export interface ParallelExecutionResult {
  results: Map<string, GenerationResult>;
  allFiles: GeneratedFile[];
  allErrors: string[];
  allWarnings: string[];
  executionTimes: Map<string, number>;
  totalDuration: number;
}

export class ParallelExecutor {
  private readonly defaultMaxConcurrency: number;

  constructor() {
    // Calculate default concurrency: CPU cores - 1, min 2, max 8
    const cpuCount = os.cpus().length;
    this.defaultMaxConcurrency = Math.max(2, Math.min(8, cpuCount - 1));
  }

  /**
   * Execute generators in parallel with intelligent batching
   */
  async execute(
    generators: Array<{ name: string; generator: Generator }>,
    context: GeneratorContext,
    options: ParallelExecutionOptions = {}
  ): Promise<ParallelExecutionResult> {
    const startTime = Date.now();
    const maxConcurrency = options.maxConcurrency || this.defaultMaxConcurrency;
    const timeout = options.timeout || 60000;

    const results = new Map<string, GenerationResult>();
    const executionTimes = new Map<string, number>();
    const allFiles: GeneratedFile[] = [];
    const allErrors: string[] = [];
    const allWarnings: string[] = [];

    // Create execution queue
    const queue = [...generators];
    let completed = 0;
    const total = generators.length;

    // Execute in batches
    while (queue.length > 0) {
      // Take up to maxConcurrency generators from queue
      const batch = queue.splice(0, maxConcurrency);

      // Execute batch in parallel
      const batchResults = await Promise.allSettled(
        batch.map(({ name, generator }) =>
          this.executeGenerator(name, generator, context, timeout)
        )
      );

      // Process results
      for (let i = 0; i < batch.length; i++) {
        const { name } = batch[i];
        const result = batchResults[i];

        completed++;

        if (result.status === 'fulfilled') {
          const { result: genResult, duration } = result.value;

          results.set(name, genResult);
          executionTimes.set(name, duration);

          // Collect files, errors, warnings
          allFiles.push(...genResult.files);
          if (genResult.errors) {
            allErrors.push(...genResult.errors.map(e => `[${name}] ${e}`));
          }
          if (genResult.warnings) {
            allWarnings.push(...genResult.warnings.map(w => `[${name}] ${w}`));
          }

          // Call progress callback
          if (options.onProgress) {
            options.onProgress(completed, total, name);
          }
        } else {
          // Generator failed
          const error = result.reason;
          const errorMessage = error instanceof Error ? error.message : String(error);

          results.set(name, {
            files: [],
            errors: [errorMessage],
          });

          allErrors.push(`[${name}] ${errorMessage}`);
          executionTimes.set(name, 0);

          // Call progress callback
          if (options.onProgress) {
            options.onProgress(completed, total, name);
          }

          // Fail fast if enabled
          if (options.failFast) {
            throw new Error(`Generator "${name}" failed: ${errorMessage}`);
          }
        }
      }
    }

    const totalDuration = Date.now() - startTime;

    return {
      results,
      allFiles,
      allErrors,
      allWarnings,
      executionTimes,
      totalDuration,
    };
  }

  /**
   * Execute a single generator with timeout
   */
  private async executeGenerator(
    name: string,
    generator: Generator,
    context: GeneratorContext,
    timeout: number
  ): Promise<{ result: GenerationResult; duration: number }> {
    const startTime = Date.now();

    // Create timeout promise with clearable timer
    let timeoutId: NodeJS.Timeout;
    const timeoutPromise = new Promise<never>((_, reject) => {
      timeoutId = setTimeout(() => {
        reject(new Error(`Generator "${name}" timed out after ${timeout}ms`));
      }, timeout);
    });

    try {
      // Race between generator execution and timeout
      const result = await Promise.race([
        Promise.resolve(generator.generate(context)),
        timeoutPromise,
      ]);

      const duration = Date.now() - startTime;

      return { result, duration };
    } finally {
      // Always clear the timeout to prevent hanging handles
      clearTimeout(timeoutId!);
    }
  }

  /**
   * Get recommended concurrency based on system resources and workload
   */
  getRecommendedConcurrency(generatorCount: number): number {
    // For small workloads, don't over-parallelize
    if (generatorCount <= 2) {
      return generatorCount;
    }

    // For medium workloads, use default
    if (generatorCount <= this.defaultMaxConcurrency) {
      return this.defaultMaxConcurrency;
    }

    // For large workloads, use max concurrency
    return Math.max(this.defaultMaxConcurrency, Math.min(8, generatorCount));
  }

  /**
   * Analyze generator dependencies (for future dependency-aware execution)
   * Currently returns empty dependencies (all generators are independent)
   */
  analyzeDependencies(
    generators: Array<{ name: string; generator: Generator }>
  ): Map<string, string[]> {
    const dependencies = new Map<string, string[]>();

    // Initialize with no dependencies
    for (const { name } of generators) {
      dependencies.set(name, []);
    }

    // Future enhancement: Analyze actual dependencies
    // e.g., backend generator might depend on database generator
    // For now, treat all generators as independent

    return dependencies;
  }

  /**
   * Create execution plan considering dependencies (for future use)
   * Currently returns simple batches since no dependencies exist
   */
  createExecutionPlan(
    generators: Array<{ name: string; generator: Generator }>,
    dependencies: Map<string, string[]>,
    maxConcurrency: number
  ): string[][] {
    const plan: string[][] = [];

    // For now, since no dependencies exist, just batch by concurrency
    const generatorNames = generators.map(g => g.name);

    for (let i = 0; i < generatorNames.length; i += maxConcurrency) {
      plan.push(generatorNames.slice(i, i + maxConcurrency));
    }

    return plan;
  }
}

/**
 * Utility class for tracking parallel execution metrics
 */
export class ExecutionMetrics {
  private metrics: Map<string, {
    duration: number;
    filesGenerated: number;
    success: boolean;
    startTime: number;
    endTime: number;
  }> = new Map();

  /**
   * Record generator execution
   */
  record(
    name: string,
    duration: number,
    filesGenerated: number,
    success: boolean
  ): void {
    const endTime = Date.now();
    const startTime = endTime - duration;

    this.metrics.set(name, {
      duration,
      filesGenerated,
      success,
      startTime,
      endTime,
    });
  }

  /**
   * Get total execution time
   */
  getTotalTime(): number {
    if (this.metrics.size === 0) return 0;

    const times = Array.from(this.metrics.values());
    const earliest = Math.min(...times.map(t => t.startTime));
    const latest = Math.max(...times.map(t => t.endTime));

    return latest - earliest;
  }

  /**
   * Get average execution time per generator
   */
  getAverageTime(): number {
    if (this.metrics.size === 0) return 0;

    const totalDuration = Array.from(this.metrics.values())
      .reduce((sum, m) => sum + m.duration, 0);

    return totalDuration / this.metrics.size;
  }

  /**
   * Get slowest generator
   */
  getSlowestGenerator(): { name: string; duration: number } | null {
    if (this.metrics.size === 0) return null;

    let slowest: { name: string; duration: number } | null = null;

    for (const [name, metrics] of this.metrics.entries()) {
      if (!slowest || metrics.duration > slowest.duration) {
        slowest = { name, duration: metrics.duration };
      }
    }

    return slowest;
  }

  /**
   * Get fastest generator
   */
  getFastestGenerator(): { name: string; duration: number } | null {
    if (this.metrics.size === 0) return null;

    let fastest: { name: string; duration: number } | null = null;

    for (const [name, metrics] of this.metrics.entries()) {
      if (!fastest || metrics.duration < fastest.duration) {
        fastest = { name, duration: metrics.duration };
      }
    }

    return fastest;
  }

  /**
   * Get success rate
   */
  getSuccessRate(): number {
    if (this.metrics.size === 0) return 0;

    const successful = Array.from(this.metrics.values())
      .filter(m => m.success).length;

    return (successful / this.metrics.size) * 100;
  }

  /**
   * Get formatted summary
   */
  getSummary(): string {
    if (this.metrics.size === 0) {
      return 'No metrics recorded';
    }

    const lines: string[] = [];
    const totalTime = this.getTotalTime();
    const avgTime = this.getAverageTime();
    const slowest = this.getSlowestGenerator();
    const fastest = this.getFastestGenerator();
    const successRate = this.getSuccessRate();

    lines.push(`Parallel Execution Summary:`);
    lines.push(`  Total generators: ${this.metrics.size}`);
    lines.push(`  Wall clock time: ${totalTime}ms`);
    lines.push(`  Average time per generator: ${Math.round(avgTime)}ms`);
    lines.push(`  Success rate: ${successRate.toFixed(1)}%`);

    if (slowest) {
      lines.push(`  Slowest: ${slowest.name} (${slowest.duration}ms)`);
    }

    if (fastest) {
      lines.push(`  Fastest: ${fastest.name} (${fastest.duration}ms)`);
    }

    // Calculate speedup if we have enough data
    const totalSequentialTime = Array.from(this.metrics.values())
      .reduce((sum, m) => sum + m.duration, 0);

    if (totalTime > 0 && totalSequentialTime > totalTime) {
      const speedup = totalSequentialTime / totalTime;
      lines.push(`  Speedup: ${speedup.toFixed(2)}x`);
    }

    return lines.join('\n');
  }

  /**
   * Get all metrics
   */
  getAll(): Map<string, {
    duration: number;
    filesGenerated: number;
    success: boolean;
    startTime: number;
    endTime: number;
  }> {
    return new Map(this.metrics);
  }
}
