/**
 * Background Task Manager
 *
 * Manages background tasks (shell commands and sub-agents) that run
 * asynchronously and can be monitored or killed.
 */

import { ChildProcess, spawn } from 'child_process';
import { randomBytes } from 'crypto';

/**
 * Task status enum
 */
export type BackgroundTaskStatus = 'running' | 'completed' | 'failed' | 'killed';

/**
 * Task type enum
 */
export type BackgroundTaskType = 'shell' | 'agent';

/**
 * Background task interface
 */
export interface BackgroundTask {
  taskId: string;
  taskType: BackgroundTaskType;
  status: BackgroundTaskStatus;
  description: string;
  startedAt: Date;
  completedAt?: Date;

  // Shell task specific
  command?: string;
  process?: ChildProcess;
  stdout: string;
  stderr: string;
  exitCode?: number;

  // Agent task specific
  agentType?: string;
  prompt?: string;
  result?: unknown;
}

/**
 * Task info returned by list/status
 */
export interface BackgroundTaskInfo {
  taskId: string;
  taskType: BackgroundTaskType;
  status: BackgroundTaskStatus;
  description: string;
  startedAt: string;
  completedAt?: string;
  command?: string;
  agentType?: string;
  exitCode?: number;
  stdout?: string;
  stderr?: string;
  result?: unknown;
}

/**
 * BackgroundTaskManager - Singleton for managing background tasks
 */
export class BackgroundTaskManager {
  private static instance: BackgroundTaskManager | null = null;

  private tasks: Map<string, BackgroundTask> = new Map();
  private maxTasks = 100; // Limit to prevent memory leaks

  private constructor() {}

  /**
   * Get singleton instance
   */
  static getInstance(): BackgroundTaskManager {
    if (!BackgroundTaskManager.instance) {
      BackgroundTaskManager.instance = new BackgroundTaskManager();
    }
    return BackgroundTaskManager.instance;
  }

  /**
   * Reset singleton (for testing)
   */
  static reset(): void {
    if (BackgroundTaskManager.instance) {
      // Kill all running tasks
      for (const task of BackgroundTaskManager.instance.tasks.values()) {
        if (task.status === 'running' && task.process) {
          task.process.kill();
        }
      }
      BackgroundTaskManager.instance.tasks.clear();
    }
    BackgroundTaskManager.instance = null;
  }

  /**
   * Generate unique task ID
   */
  private generateTaskId(type: BackgroundTaskType): string {
    const prefix = type === 'shell' ? 'shell' : 'agent';
    const suffix = randomBytes(6).toString('hex');
    return `${prefix}_${suffix}`;
  }

  /**
   * Clean old completed tasks if limit exceeded
   */
  private cleanOldTasks(): void {
    if (this.tasks.size <= this.maxTasks) return;

    // Get completed/failed tasks sorted by completion time
    const completedTasks = Array.from(this.tasks.entries())
      .filter(([_, task]) => task.status !== 'running')
      .sort((a, b) => {
        const aTime = a[1].completedAt?.getTime() ?? 0;
        const bTime = b[1].completedAt?.getTime() ?? 0;
        return aTime - bTime;
      });

    // Remove oldest completed tasks
    const toRemove = completedTasks.slice(0, this.tasks.size - this.maxTasks + 10);
    for (const [taskId] of toRemove) {
      this.tasks.delete(taskId);
    }
  }

  /**
   * Start a shell command as a background task
   */
  startShellTask(
    command: string,
    options: {
      cwd?: string;
      description?: string;
      env?: Record<string, string>;
    } = {}
  ): string {
    this.cleanOldTasks();

    const taskId = this.generateTaskId('shell');
    const task: BackgroundTask = {
      taskId,
      taskType: 'shell',
      status: 'running',
      description: options.description ?? command.slice(0, 50),
      startedAt: new Date(),
      command,
      stdout: '',
      stderr: '',
    };

    // Spawn the process
    const proc = spawn('bash', ['-c', command], {
      cwd: options.cwd,
      env: { ...process.env, ...options.env },
      stdio: ['ignore', 'pipe', 'pipe'],
    });

    task.process = proc;

    // Collect stdout
    proc.stdout?.on('data', (data: Buffer) => {
      task.stdout += data.toString();
      // Limit buffer size
      if (task.stdout.length > 100000) {
        task.stdout = task.stdout.slice(-50000);
      }
    });

    // Collect stderr
    proc.stderr?.on('data', (data: Buffer) => {
      task.stderr += data.toString();
      if (task.stderr.length > 100000) {
        task.stderr = task.stderr.slice(-50000);
      }
    });

    // Handle completion
    proc.on('close', (code) => {
      task.exitCode = code ?? 0;
      task.status = code === 0 ? 'completed' : 'failed';
      task.completedAt = new Date();
      task.process = undefined; // Clear process reference
    });

    // Handle error
    proc.on('error', (err) => {
      task.stderr += `\nProcess error: ${err.message}`;
      task.status = 'failed';
      task.completedAt = new Date();
      task.process = undefined;
    });

    this.tasks.set(taskId, task);
    return taskId;
  }

  /**
   * Register an agent task (started externally)
   */
  registerAgentTask(
    agentType: string,
    prompt: string,
    options: { description?: string } = {}
  ): string {
    this.cleanOldTasks();

    const taskId = this.generateTaskId('agent');
    const task: BackgroundTask = {
      taskId,
      taskType: 'agent',
      status: 'running',
      description: options.description ?? `${agentType} agent`,
      startedAt: new Date(),
      agentType,
      prompt,
      stdout: '',
      stderr: '',
    };

    this.tasks.set(taskId, task);
    return taskId;
  }

  /**
   * Complete an agent task
   */
  completeAgentTask(taskId: string, result: unknown, success: boolean): void {
    const task = this.tasks.get(taskId);
    if (!task) return;

    task.status = success ? 'completed' : 'failed';
    task.completedAt = new Date();
    task.result = result;
  }

  /**
   * Get a task by ID
   */
  getTask(taskId: string): BackgroundTask | null {
    return this.tasks.get(taskId) ?? null;
  }

  /**
   * Get all tasks
   */
  getAllTasks(): BackgroundTask[] {
    return Array.from(this.tasks.values());
  }

  /**
   * Get tasks filtered by status
   */
  getTasksByStatus(status: BackgroundTaskStatus): BackgroundTask[] {
    return Array.from(this.tasks.values()).filter((t) => t.status === status);
  }

  /**
   * Kill a running task
   */
  killTask(taskId: string): boolean {
    const task = this.tasks.get(taskId);
    if (!task || task.status !== 'running') return false;

    if (task.taskType === 'shell' && task.process) {
      task.process.kill('SIGTERM');
      // Force kill after timeout
      setTimeout(() => {
        if (task.process && task.status === 'running') {
          task.process.kill('SIGKILL');
        }
      }, 5000);
    }

    task.status = 'killed';
    task.completedAt = new Date();
    return true;
  }

  /**
   * Convert task to info object (for API responses)
   */
  taskToInfo(task: BackgroundTask): BackgroundTaskInfo {
    return {
      taskId: task.taskId,
      taskType: task.taskType,
      status: task.status,
      description: task.description,
      startedAt: task.startedAt.toISOString(),
      completedAt: task.completedAt?.toISOString(),
      command: task.command,
      agentType: task.agentType,
      exitCode: task.exitCode,
      stdout: task.stdout,
      stderr: task.stderr,
      result: task.result,
    };
  }
}

/**
 * Get the background task manager singleton
 */
export function getBackgroundTaskManager(): BackgroundTaskManager {
  return BackgroundTaskManager.getInstance();
}
