/**
 * @fileoverview OrdoJS CLI - Process Manager
 *
 * Handles process lifecycle management, cleanup, and zombie prevention.
 */

import { ChildProcess, spawn } from 'child_process';
import { logger } from '../utils/index.js';

/**
 * ProcessManager class for managing child processes
 */
export class ProcessManager {
  private processes: Map<string, ChildProcess>;
  private isShuttingDown: boolean;
  private exitHandlerRegistered: boolean;

  /**
   * Create a new ProcessManager instance
   */
  constructor() {
    this.processes = new Map();
    this.isShuttingDown = false;
    this.exitHandlerRegistered = false;
    this.registerExitHandlers();
  }

  /**
   * Register handlers for process exit events
   */
  private registerExitHandlers(): void {
    if (this.exitHandlerRegistered) {
      return;
    }

    // Handle normal exit
    process.on('exit', () => {
      this.cleanup();
    });

    // Handle Ctrl+C and other signals
    process.on('SIGINT', () => {
      this.shutdown('SIGINT received');
    });

    process.on('SIGTERM', () => {
      this.shutdown('SIGTERM received');
    });

    // Handle uncaught exceptions
    process.on('uncaughtException', (error) => {
      logger.error(`Uncaught exception: ${error.message}`);
      this.shutdown('Uncaught exception');
    });

    this.exitHandlerRegistered = true;
  }

  /**
   * Start a new child process
   *
   * @param id - Unique identifier for the process
   * @param command - Command to execute
   * @param args - Command arguments
   * @param options - Spawn options
   * @returns The created child process
   */
  startProcess(
    id: string,
    command: string,
    args: string[] = [],
    options: Record<string, any> = {}
  ): ChildProcess {
    if (this.isShuttingDown) {
      throw new Error('Cannot start new process during shutdown');
    }

    logger.debug(`Starting process ${id}: ${command} ${args.join(' ')}`);

    const childProcess = spawn(command, args, {
      stdio: 'pipe',
      ...options
    });

    // Store the process
    this.processes.set(id, childProcess);

    // Set up event handlers
    childProcess.on('error', (error) => {
      logger.error(`Process ${id} error: ${error.message}`);
    });

    childProcess.on('exit', (code, signal) => {
      logger.debug(`Process ${id} exited with code ${code} and signal ${signal}`);
      this.processes.delete(id);
    });

    // Handle stdout and stderr
    if (childProcess.stdout) {
      childProcess.stdout.on('data', (data) => {
        logger.debug(`[${id}] ${data.toString().trim()}`);
      });
    }

    if (childProcess.stderr) {
      childProcess.stderr.on('data', (data) => {
        logger.error(`[${id}] ${data.toString().trim()}`);
      });
    }

    return childProcess;
  }

  /**
   * Stop a specific process
   *
   * @param id - Process identifier
   * @param signal - Signal to send (default: SIGTERM)
   * @returns Promise that resolves when the process has exited
   */
  async stopProcess(id: string, signal: NodeJS.Signals = 'SIGTERM'): Promise<void> {
    const childProcess = this.processes.get(id);

    if (!childProcess) {
      logger.debug(`Process ${id} not found or already stopped`);
      return;
    }

    return new Promise<void>((resolve) => {
      // Set up exit handler
      childProcess.once('exit', () => {
        this.processes.delete(id);
        logger.debug(`Process ${id} stopped`);
        resolve();
      });

      // Try to kill the process gracefully
      if (childProcess.kill(signal)) {
        logger.debug(`Sent ${signal} to process ${id}`);
      } else {
        logger.warn(`Failed to send ${signal} to process ${id}, may already be exiting`);
        this.processes.delete(id);
        resolve();
      }

      // Set a timeout to force kill if it doesn't exit
      setTimeout(() => {
        if (this.processes.has(id)) {
          logger.warn(`Process ${id} did not exit after ${signal}, forcing SIGKILL`);
          childProcess.kill('SIGKILL');
        }
      }, 5000);
    });
  }

  /**
   * Check if a process is running
   *
   * @param id - Process identifier
   * @returns True if the process is running
   */
  isProcessRunning(id: string): boolean {
    return this.processes.has(id);
  }

  /**
   * Get all running processes
   *
   * @returns Map of process IDs to child processes
   */
  getRunningProcesses(): Map<string, ChildProcess> {
    return new Map(this.processes);
  }

  /**
   * Clean up all managed processes
   */
  async cleanup(): Promise<void> {
    if (this.processes.size === 0) {
      return;
    }

    logger.debug(`Cleaning up ${this.processes.size} processes`);

    // Create an array of promises for stopping each process
    const stopPromises = Array.from(this.processes.keys()).map(id =>
      this.stopProcess(id).catch(error => {
        logger.error(`Error stopping process ${id}: ${error instanceof Error ? error.message : String(error)}`);
      })
    );

    // Wait for all processes to stop
    await Promise.all(stopPromises);

    // Double-check that all processes are gone
    if (this.processes.size > 0) {
      logger.warn(`${this.processes.size} processes could not be stopped gracefully, forcing termination`);

      // Force kill any remaining processes
      for (const [id, childProcess] of this.processes.entries()) {
        childProcess.kill('SIGKILL');
        this.processes.delete(id);
      }
    }
  }

  /**
   * Shutdown the process manager and all managed processes
   *
   * @param reason - Reason for shutdown
   */
  async shutdown(reason: string): Promise<void> {
    if (this.isShuttingDown) {
      return;
    }

    this.isShuttingDown = true;
    logger.info(`Shutting down: ${reason}`);

    try {
      await this.cleanup();
      logger.success('All processes terminated successfully');
    } catch (error) {
      logger.error(`Error during shutdown: ${error instanceof Error ? error.message : String(error)}`);
    }

    // Exit the process
    process.exit(0);
  }
}
