import { exec } from 'child_process';
import { promisify } from 'util';
import { EventEmitter } from 'events';
import {
  Task,
  TaskCommand,
  TaskFunction,
  TaskGroup,
  Workflow,
  WorkflowExecution,
  TaskExecution,
  ExecutionLog,
  OrchestrationConfig,
  TaskDependency,
  DependencyCondition,
} from './types.js';

const execAsync = promisify(exec);

export class TaskOrchestrator extends EventEmitter {
  private config: OrchestrationConfig;
  private runningTasks: Map<string, TaskExecution>;
  private taskQueue: Task[];
  private workflows: Map<string, Workflow>;
  private executions: Map<string, WorkflowExecution>;

  constructor(config?: Partial<OrchestrationConfig>) {
    super();
    this.config = {
      maxConcurrentTasks: config?.maxConcurrentTasks || 5,
      defaultTimeout: config?.defaultTimeout || 300000, // 5 minutes
      retryPolicy: {
        maxRetries: config?.retryPolicy?.maxRetries || 3,
        backoffMultiplier: config?.retryPolicy?.backoffMultiplier || 2,
        initialDelay: config?.retryPolicy?.initialDelay || 1000,
        maxDelay: config?.retryPolicy?.maxDelay || 30000,
      },
      executionMode: config?.executionMode || 'local',
      persistence: {
        enabled: config?.persistence?.enabled || true,
        path: config?.persistence?.path || '.atlas/workflows',
      },
    };

    this.runningTasks = new Map();
    this.taskQueue = [];
    this.workflows = new Map();
    this.executions = new Map();
  }

  async createWorkflow(workflow: Omit<Workflow, 'id' | 'createdAt' | 'updatedAt'>): Promise<Workflow> {
    const newWorkflow: Workflow = {
      ...workflow,
      id: crypto.randomUUID(),
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    };

    // Validate workflow
    this.validateWorkflow(newWorkflow);

    this.workflows.set(newWorkflow.id, newWorkflow);
    this.emit('workflow:created', newWorkflow);

    return newWorkflow;
  }

  async executeWorkflow(workflowId: string, variables?: Record<string, any>): Promise<WorkflowExecution> {
    const workflow = this.workflows.get(workflowId);
    if (!workflow) {
      throw new Error(`Workflow ${workflowId} not found`);
    }

    const execution: WorkflowExecution = {
      id: crypto.randomUUID(),
      workflowId: workflow.id,
      workflowVersion: workflow.version,
      status: 'pending',
      startTime: new Date().toISOString(),
      tasks: [],
      variables: { ...workflow.variables, ...variables },
      logs: [],
    };

    this.executions.set(execution.id, execution);
    this.emit('execution:started', execution);

    try {
      execution.status = 'running';
      await this.runWorkflow(workflow, execution);
      
      execution.status = 'completed';
      execution.endTime = new Date().toISOString();
      execution.duration = new Date(execution.endTime).getTime() - new Date(execution.startTime).getTime();
      
      this.emit('execution:completed', execution);
    } catch (error: any) {
      execution.status = 'failed';
      execution.error = error.message;
      execution.endTime = new Date().toISOString();
      execution.duration = new Date(execution.endTime).getTime() - new Date(execution.startTime).getTime();
      
      this.emit('execution:failed', execution);
      throw error;
    }

    return execution;
  }

  async executeTask(task: Task, variables?: Record<string, any>): Promise<TaskExecution> {
    const execution: TaskExecution = {
      taskId: task.id,
      executionId: crypto.randomUUID(),
      status: 'pending',
      retryCount: 0,
      logs: [],
    };

    this.emit('task:started', { task, execution });

    try {
      execution.status = 'running';
      execution.startTime = new Date().toISOString();

      let result: any;
      switch (task.type) {
        case 'command':
          result = await this.executeCommand(task, variables);
          break;
        case 'function':
          result = await this.executeFunction(task, variables);
          break;
        case 'manual':
          result = await this.waitForManualCompletion(task);
          break;
        case 'parallel':
          result = await this.executeParallelTasks(task, variables);
          break;
        case 'sequential':
          result = await this.executeSequentialTasks(task, variables);
          break;
        default:
          throw new Error(`Unknown task type: ${task.type}`);
      }

      execution.output = result;
      execution.status = 'completed';
      execution.endTime = new Date().toISOString();
      execution.duration = new Date(execution.endTime).getTime() - new Date(execution.startTime).getTime();

      this.emit('task:completed', { task, execution });
    } catch (error: any) {
      execution.error = error.message;
      execution.status = 'failed';
      execution.endTime = new Date().toISOString();
      execution.duration = execution.startTime 
        ? new Date(execution.endTime).getTime() - new Date(execution.startTime).getTime()
        : 0;

      // Retry logic
      if (execution.retryCount < task.maxRetries) {
        execution.retryCount++;
        const delay = this.calculateRetryDelay(execution.retryCount);
        
        this.emit('task:retrying', { task, execution, delay });
        await new Promise(resolve => setTimeout(resolve, delay));
        
        return this.executeTask(task, variables);
      }

      this.emit('task:failed', { task, execution });
      throw error;
    }

    return execution;
  }

  async cancelExecution(executionId: string): Promise<void> {
    const execution = this.executions.get(executionId);
    if (!execution) {
      throw new Error(`Execution ${executionId} not found`);
    }

    execution.status = 'cancelled';
    execution.endTime = new Date().toISOString();
    
    // Cancel all running tasks
    for (const task of execution.tasks) {
      if (task.status === 'running' || task.status === 'pending') {
        task.status = 'cancelled';
      }
    }

    this.emit('execution:cancelled', execution);
  }

  getWorkflowStatus(workflowId: string): Workflow | undefined {
    return this.workflows.get(workflowId);
  }

  getExecutionStatus(executionId: string): WorkflowExecution | undefined {
    return this.executions.get(executionId);
  }

  listWorkflows(): Workflow[] {
    return Array.from(this.workflows.values());
  }

  listExecutions(workflowId?: string): WorkflowExecution[] {
    const executions = Array.from(this.executions.values());
    if (workflowId) {
      return executions.filter(e => e.workflowId === workflowId);
    }
    return executions;
  }

  private async runWorkflow(workflow: Workflow, execution: WorkflowExecution): Promise<void> {
    // Build dependency graph
    const dependencyGraph = this.buildDependencyGraph(workflow.tasks);
    
    // Execute tasks based on dependencies
    const completed = new Set<string>();
    const failed = new Set<string>();

    while (completed.size + failed.size < workflow.tasks.length) {
      // Find tasks ready to execute
      const readyTasks = workflow.tasks.filter(task => {
        if (completed.has(task.id) || failed.has(task.id)) return false;
        
        // Check if all dependencies are satisfied
        return task.dependencies.every(dep => {
          if (failed.has(dep)) {
            // If dependency failed, check if we should skip this task
            return false;
          }
          return completed.has(dep);
        });
      });

      if (readyTasks.length === 0) {
        // No tasks ready, check for deadlock
        const remainingTasks = workflow.tasks.filter(
          t => !completed.has(t.id) && !failed.has(t.id)
        );
        if (remainingTasks.length > 0) {
          throw new Error('Workflow deadlock detected');
        }
        break;
      }

      // Execute ready tasks (respecting concurrency limit)
      const tasksToRun = readyTasks.slice(0, this.config.maxConcurrentTasks - this.runningTasks.size);
      
      await Promise.all(tasksToRun.map(async task => {
        try {
          const taskExecution = await this.executeTask(task, execution.variables);
          execution.tasks.push(taskExecution);
          completed.add(task.id);
        } catch (error) {
          failed.add(task.id);
          // Continue or fail based on task configuration
          if (!task.metadata?.continueOnError) {
            throw error;
          }
        }
      }));
    }
  }

  private async executeCommand(task: Task, variables?: Record<string, any>): Promise<any> {
    const config = task.input as TaskCommand;
    
    let command = this.interpolateVariables(config.command, variables);
    const options: any = {
      cwd: config.workingDirectory,
      env: { ...process.env, ...config.environment },
      shell: config.shell,
      timeout: task.timeout || this.config.defaultTimeout,
    };

    try {
      const { stdout, stderr } = await execAsync(command, options);
      
      if (config.captureOutput) {
        return { stdout, stderr };
      }
      
      return stdout;
    } catch (error: any) {
      throw new Error(`Command failed: ${error.message}`);
    }
  }

  private async executeFunction(task: Task, variables?: Record<string, any>): Promise<any> {
    const config = task.input as TaskFunction;
    
    // This is a placeholder - in a real implementation, you'd have a function registry
    throw new Error('Function execution not implemented');
  }

  private async waitForManualCompletion(task: Task): Promise<any> {
    // This would typically integrate with a UI or API
    return new Promise((resolve, reject) => {
      this.once(`task:manual:${task.id}`, (result) => {
        if (result.error) {
          reject(new Error(result.error));
        } else {
          resolve(result.output);
        }
      });
    });
  }

  private async executeParallelTasks(task: Task, variables?: Record<string, any>): Promise<any[]> {
    const subtasks = task.input as Task[];
    const results = await Promise.all(
      subtasks.map(subtask => this.executeTask(subtask, variables))
    );
    return results.map(r => r.output);
  }

  private async executeSequentialTasks(task: Task, variables?: Record<string, any>): Promise<any[]> {
    const subtasks = task.input as Task[];
    const results: any[] = [];
    
    for (const subtask of subtasks) {
      const result = await this.executeTask(subtask, variables);
      results.push(result.output);
    }
    
    return results;
  }

  private validateWorkflow(workflow: Workflow): void {
    // Check for circular dependencies
    const visited = new Set<string>();
    const recursionStack = new Set<string>();

    const hasCycle = (taskId: string): boolean => {
      visited.add(taskId);
      recursionStack.add(taskId);

      const task = workflow.tasks.find(t => t.id === taskId);
      if (!task) return false;

      for (const dep of task.dependencies) {
        if (!visited.has(dep)) {
          if (hasCycle(dep)) return true;
        } else if (recursionStack.has(dep)) {
          return true;
        }
      }

      recursionStack.delete(taskId);
      return false;
    };

    for (const task of workflow.tasks) {
      if (!visited.has(task.id) && hasCycle(task.id)) {
        throw new Error('Workflow contains circular dependencies');
      }
    }

    // Validate entry point exists
    const entryTask = workflow.tasks.find(t => t.id === workflow.entryPoint);
    const entryGroup = workflow.groups.find(g => g.id === workflow.entryPoint);
    
    if (!entryTask && !entryGroup) {
      throw new Error('Workflow entry point not found');
    }
  }

  private buildDependencyGraph(tasks: Task[]): Map<string, Set<string>> {
    const graph = new Map<string, Set<string>>();
    
    for (const task of tasks) {
      if (!graph.has(task.id)) {
        graph.set(task.id, new Set());
      }
      
      for (const dep of task.dependencies) {
        const depSet = graph.get(dep) || new Set();
        depSet.add(task.id);
        graph.set(dep, depSet);
      }
    }
    
    return graph;
  }

  private calculateRetryDelay(retryCount: number): number {
    const delay = Math.min(
      this.config.retryPolicy.initialDelay * Math.pow(this.config.retryPolicy.backoffMultiplier, retryCount - 1),
      this.config.retryPolicy.maxDelay
    );
    return delay;
  }

  private interpolateVariables(template: string, variables?: Record<string, any>): string {
    if (!variables) return template;
    
    return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
      const value = this.getNestedValue(variables, key);
      return value !== undefined ? String(value) : match;
    });
  }

  private getNestedValue(obj: any, path: string): any {
    const keys = path.split('.');
    let current = obj;
    
    for (const key of keys) {
      if (current && typeof current === 'object' && key in current) {
        current = current[key];
      } else {
        return undefined;
      }
    }
    
    return current;
  }

  completeManualTask(taskId: string, output?: any, error?: string): void {
    this.emit(`task:manual:${taskId}`, { output, error });
  }
}