import { EventEmitter } from 'events';
import { 
  ProcessDefinition, 
  ProcessExecution, 
  ExecutionStatus,
  Activity,
  ActivityResult,
  ProcessError,
  LogEntry,
  ActivityType
} from './types.js';
import { ActivityExecutor } from './activity-executor.js';
import { VariableResolver } from './variable-resolver.js';
import { ProcessStore } from './process-store.js';
// import { AgentOrchestrator } from '../agent-orchestration/orchestrator.js';

export class ProcessEngine extends EventEmitter {
  private executions: Map<string, ProcessExecution> = new Map();
  private activityExecutor: ActivityExecutor;
  private variableResolver: VariableResolver;
  private store: ProcessStore;
  // private agentOrchestrator: AgentOrchestrator;

  constructor(store: ProcessStore, agentOrchestrator?: any /*AgentOrchestrator*/) {
    super();
    this.store = store;
    // this.agentOrchestrator = agentOrchestrator;
    this.activityExecutor = new ActivityExecutor(this, agentOrchestrator);
    this.variableResolver = new VariableResolver();
  }

  async executeProcess(
    process: ProcessDefinition, 
    triggerId: string = 'manual',
    initialVariables: Record<string, any> = {}
  ): Promise<ProcessExecution> {
    const execution: ProcessExecution = {
      id: this.generateExecutionId(),
      processId: process.id,
      processVersion: process.version,
      status: 'pending',
      triggeredBy: triggerId,
      startedAt: new Date().toISOString(),
      variables: { ...process.variables, ...initialVariables },
      activityResults: [],
      logs: []
    };

    this.executions.set(execution.id, execution);
    await this.store.saveExecution(execution);

    this.log(execution.id, 'info', `Starting process: ${process.name}`);
    this.emit('execution:started', execution);

    try {
      execution.status = 'running';
      await this.store.saveExecution(execution);

      // Execute activities sequentially
      for (const activity of process.activities) {
        if (execution.status !== 'running') {
          break; // Process was paused or cancelled
        }

        await this.executeActivity(execution, activity);
      }

      if (execution.status === 'running') {
        execution.status = 'completed';
        execution.completedAt = new Date().toISOString();
        execution.duration = Date.now() - new Date(execution.startedAt).getTime();

        // Execute success handlers if any
        if (process.onSuccess) {
          for (const activity of process.onSuccess) {
            await this.executeActivity(execution, activity);
          }
        }
      }
    } catch (error) {
      execution.status = 'failed';
      execution.error = this.createProcessError(error);
      execution.completedAt = new Date().toISOString();
      execution.duration = Date.now() - new Date(execution.startedAt).getTime();

      this.log(execution.id, 'error', `Process failed: ${execution.error.message}`);

      // Execute failure handlers if any
      if (process.onFailure) {
        for (const activity of process.onFailure) {
          try {
            await this.executeActivity(execution, activity);
          } catch (handlerError) {
            this.log(execution.id, 'error', `Failure handler error: ${handlerError}`);
          }
        }
      }
    }

    await this.store.saveExecution(execution);
    this.emit('execution:completed', execution);

    return execution;
  }

  private async executeActivity(
    execution: ProcessExecution,
    activity: Activity
  ): Promise<ActivityResult> {
    // Check condition if specified
    if (activity.condition) {
      const shouldRun = await this.variableResolver.evaluateCondition(
        activity.condition,
        execution.variables
      );

      if (!shouldRun) {
        const result: ActivityResult = {
          activityId: activity.id,
          status: 'skipped',
          startedAt: new Date().toISOString(),
          completedAt: new Date().toISOString(),
          duration: 0
        };

        execution.activityResults.push(result);
        this.log(execution.id, 'info', `Skipped activity: ${activity.name} (condition not met)`);
        return result;
      }
    }

    this.log(execution.id, 'info', `Executing activity: ${activity.name}`, activity.id);
    this.emit('activity:started', { execution, activity });

    const startTime = Date.now();
    const result: ActivityResult = {
      activityId: activity.id,
      status: 'success',
      startedAt: new Date().toISOString(),
      completedAt: '',
      duration: 0
    };

    try {
      // Resolve input variables
      const resolvedInputs = await this.variableResolver.resolveVariables(
        activity.inputs || {},
        execution.variables
      );

      // Execute based on activity type
      const outputs = await this.activityExecutor.execute(
        activity,
        resolvedInputs,
        execution
      );

      result.outputs = outputs;
      result.status = 'success';

      // Store outputs in execution variables
      if (activity.outputs && outputs) {
        for (const outputVar of activity.outputs) {
          execution.variables[outputVar] = outputs[outputVar];
        }
      }
    } catch (error) {
      result.status = 'failed';
      result.error = error instanceof Error ? error.message : String(error);

      // Handle error based on error handler configuration
      if (activity.errorHandler) {
        await this.handleActivityError(execution, activity, error);
      } else {
        throw error; // Propagate to process level
      }
    } finally {
      result.completedAt = new Date().toISOString();
      result.duration = Date.now() - startTime;
      execution.activityResults.push(result);

      this.emit('activity:completed', { execution, activity, result });
    }

    return result;
  }

  private async handleActivityError(
    execution: ProcessExecution,
    activity: Activity,
    error: any
  ): Promise<void> {
    const handler = activity.errorHandler!;

    switch (handler.type) {
      case 'retry':
        // Implement retry logic
        if (handler.retryPolicy) {
          // TODO: Implement retry with backoff
          this.log(execution.id, 'warn', `Retrying activity: ${activity.name}`);
        }
        break;

      case 'skip':
        this.log(execution.id, 'warn', `Skipping failed activity: ${activity.name}`);
        break;

      case 'alternate':
        if (handler.alternateActivities) {
          for (const altActivity of handler.alternateActivities) {
            await this.executeActivity(execution, altActivity);
          }
        }
        break;

      case 'fail':
      default:
        throw error;
    }
  }

  async pauseExecution(executionId: string): Promise<void> {
    const execution = this.executions.get(executionId);
    if (execution && execution.status === 'running') {
      execution.status = 'paused';
      await this.store.saveExecution(execution);
      this.emit('execution:paused', execution);
    }
  }

  async resumeExecution(executionId: string): Promise<void> {
    const execution = this.executions.get(executionId);
    if (execution && execution.status === 'paused') {
      execution.status = 'running';
      await this.store.saveExecution(execution);
      this.emit('execution:resumed', execution);
      // TODO: Continue execution from where it left off
    }
  }

  async cancelExecution(executionId: string): Promise<void> {
    const execution = this.executions.get(executionId);
    if (execution && ['running', 'paused', 'waiting'].includes(execution.status)) {
      execution.status = 'cancelled';
      execution.completedAt = new Date().toISOString();
      execution.duration = Date.now() - new Date(execution.startedAt).getTime();
      await this.store.saveExecution(execution);
      this.emit('execution:cancelled', execution);
    }
  }

  getExecution(executionId: string): ProcessExecution | undefined {
    return this.executions.get(executionId);
  }

  private log(
    executionId: string,
    level: LogEntry['level'],
    message: string,
    activityId?: string
  ): void {
    const execution = this.executions.get(executionId);
    if (execution) {
      const entry: LogEntry = {
        timestamp: new Date().toISOString(),
        level,
        message,
        activityId
      };

      execution.logs.push(entry);
      this.emit('execution:log', { executionId, entry });
    }
  }

  private createProcessError(error: any): ProcessError {
    return {
      message: error instanceof Error ? error.message : String(error),
      code: error.code,
      stack: error instanceof Error ? error.stack : undefined,
      retryable: false // TODO: Determine based on error type
    };
  }

  private generateExecutionId(): string {
    return `exec-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
  }
}