import { Injectable, Logger } from '@nestjs/common';
import { spawn, ChildProcess } from 'child_process';

export interface CliProcessOptions {
  args: string[];
  env?: Record<string, string>;
  cwd?: string;
}

/**
 * Manages Claude CLI child processes.
 * Handles spawning, tracking, and killing of CLI processes per session.
 */
@Injectable()
export class CliProcessManager {
  private readonly logger = new Logger(CliProcessManager.name);
  private readonly processes = new Map<string, ChildProcess>();

  /**
   * Spawns a new Claude CLI process for the given session.
   */
  spawn(sessionId: string, options: CliProcessOptions): ChildProcess {
    const existing = this.processes.get(sessionId);
    if (existing && !existing.killed) {
      this.logger.warn(
        `Process already exists for session ${sessionId}, killing old process`,
      );
      this.kill(sessionId);
    }

    const childProcess = spawn('claude', options.args, {
      env: { ...process.env, ...options.env },
      stdio: ['pipe', 'pipe', 'pipe'],
      cwd: options.cwd,
    });

    this.processes.set(sessionId, childProcess);

    childProcess.on('exit', () => {
      this.processes.delete(sessionId);
    });

    childProcess.on('error', (err: Error) => {
      this.logger.error(
        `Process error for session ${sessionId}: ${err.message}`,
      );
      this.processes.delete(sessionId);
    });

    this.logger.debug(`Spawned CLI process for session ${sessionId} (PID: ${childProcess.pid})`);

    return childProcess;
  }

  /**
   * Sends SIGTERM to the process, falling back to SIGKILL after timeout.
   */
  kill(sessionId: string, timeoutMs = 5000): void {
    const childProcess = this.processes.get(sessionId);
    if (!childProcess) {
      this.logger.warn(`No process found for session ${sessionId}`);
      return;
    }

    childProcess.kill('SIGTERM');

    const killTimeout = setTimeout(() => {
      if (!childProcess.killed) {
        this.logger.warn(
          `Process for session ${sessionId} did not terminate gracefully, sending SIGKILL`,
        );
        childProcess.kill('SIGKILL');
      }
    }, timeoutMs);

    childProcess.once('exit', () => {
      clearTimeout(killTimeout);
    });

    this.processes.delete(sessionId);
  }

  /**
   * Returns the running process for the given session, or undefined.
   */
  getProcess(sessionId: string): ChildProcess | undefined {
    return this.processes.get(sessionId);
  }

  /**
   * Returns all active session IDs.
   */
  getActiveSessions(): string[] {
    return [...this.processes.keys()];
  }

  /**
   * Checks if a process is running for the given session.
   */
  isRunning(sessionId: string): boolean {
    const proc = this.processes.get(sessionId);
    return !!proc && !proc.killed;
  }

  /**
   * Kills all active processes. Used during shutdown.
   */
  killAll(): void {
    for (const sessionId of this.processes.keys()) {
      this.kill(sessionId);
    }
  }
}
