import { EventEmitter } from 'events';
import { ProcessEngine } from './process-engine.js';
import { ProcessStore } from './process-store.js';
import { ProcessDefinition, ProcessTrigger, TriggerType } from './types.js';
import { CronAdapter, NodeCronAdapter, CronTask } from './cron-adapter.js';

interface ScheduledTrigger {
  processId: string;
  triggerId: string;
  interval?: NodeJS.Timeout;
  nextRun?: Date;
}

interface TriggerExecution {
  triggerId: string;
  processId: string;
  executionId: string;
  executedAt: string;
  status: 'success' | 'failed';
}

export class TriggerManager extends EventEmitter {
  private engine: ProcessEngine;
  private store: ProcessStore;
  private scheduledTriggers: Map<string, ScheduledTrigger> = new Map();
  private activeTriggers: Map<string, ProcessTrigger> = new Map();
  private triggerHistory: Map<string, TriggerExecution[]> = new Map();
  private isRunning: boolean = false;
  private checkInterval?: NodeJS.Timeout;
  private cronAdapter: CronAdapter;

  constructor(engine: ProcessEngine, store: ProcessStore, cronAdapter?: CronAdapter) {
    super();
    this.engine = engine;
    this.store = store;
    this.cronAdapter = cronAdapter || new NodeCronAdapter();
  }

  async initialize(): Promise<void> {
    await this.start();
  }

  async start(): Promise<void> {
    if (this.isRunning) return;
    
    this.isRunning = true;
    await this.loadTriggers();
    
    // Check for triggers every minute
    this.checkInterval = setInterval(() => {
      this.checkTriggers();
    }, 60000);
    
    // Initial check
    this.checkTriggers();
  }

  async stop(): Promise<void> {
    this.isRunning = false;
    
    if (this.checkInterval) {
      clearInterval(this.checkInterval);
    }
    
    // Clear all scheduled triggers
    for (const scheduled of this.scheduledTriggers.values()) {
      if (scheduled.interval) {
        // If it's a cron task, call destroy
        if ((scheduled.interval as any).destroy) {
          (scheduled.interval as any).destroy();
        } else {
          clearInterval(scheduled.interval);
        }
      }
    }
    
    this.scheduledTriggers.clear();
  }

  private async loadTriggers(): Promise<void> {
    const processes = await this.store.getAllProcesses();
    
    for (const process of processes) {
      for (const trigger of process.triggers) {
        if (trigger.enabled) {
          await this.registerTrigger(process, trigger);
        }
      }
    }
  }

  private async registerTriggerInternal(
    process: ProcessDefinition,
    trigger: ProcessTrigger
  ): Promise<void> {
    const key = `${process.id}:${trigger.id}`;
    
    switch (trigger.type) {
      case 'schedule':
        this.registerScheduleTrigger(process, trigger);
        break;
        
      case 'event':
        // Register event listeners
        this.registerEventTrigger(process, trigger);
        break;
        
      case 'webhook':
        // Register webhook endpoint
        this.registerWebhookTrigger(process, trigger);
        break;
        
      case 'condition':
        // Add to condition check list
        this.registerConditionTrigger(process, trigger);
        break;
        
      case 'manual':
        // No registration needed for manual triggers
        break;
    }
  }

  private registerScheduleTrigger(
    process: ProcessDefinition,
    trigger: ProcessTrigger
  ): void {
    if (!trigger.config.cron) return;
    
    const key = `${process.id}:${trigger.id}`;
    
    // Simple cron parser for demo (in production use node-cron)
    const schedule = this.parseCronExpression(trigger.config.cron);
    
    if (schedule.interval) {
      const scheduled: ScheduledTrigger = {
        processId: process.id,
        triggerId: trigger.id,
        interval: setInterval(() => {
          this.executeTrigger(process.id, trigger.id);
        }, schedule.interval),
        nextRun: schedule.nextRun
      };
      
      this.scheduledTriggers.set(key, scheduled);
    }
  }

  private registerEventTrigger(
    process: ProcessDefinition,
    trigger: ProcessTrigger
  ): void {
    // In a real implementation, this would register with an event bus
    console.log(`Registered event trigger: ${trigger.config.event} for process ${process.id}`);
  }

  private registerWebhookTrigger(
    process: ProcessDefinition,
    trigger: ProcessTrigger
  ): void {
    // In a real implementation, this would register an HTTP endpoint
    console.log(`Registered webhook trigger: ${trigger.config.endpoint} for process ${process.id}`);
  }

  private registerConditionTrigger(
    process: ProcessDefinition,
    trigger: ProcessTrigger
  ): void {
    // Add to list of conditions to check periodically
    console.log(`Registered condition trigger for process ${process.id}`);
  }

  private async checkTriggers(): Promise<void> {
    // Check condition-based triggers
    const processes = await this.store.getAllProcesses();
    
    for (const process of processes) {
      for (const trigger of process.triggers) {
        if (trigger.type === 'condition' && trigger.enabled) {
          await this.checkConditionTrigger(process, trigger);
        }
      }
    }
  }

  private async checkConditionTrigger(
    process: ProcessDefinition,
    trigger: ProcessTrigger
  ): Promise<void> {
    // Evaluate condition
    // In a real implementation, this would evaluate the condition expression
    const conditionMet = false; // Placeholder
    
    if (conditionMet) {
      await this.executeTrigger(process.id, trigger.id);
    }
  }


  private parseCronExpression(cron: string): { interval?: number; nextRun?: Date } {
    // Very simplified cron parser for demo
    // In production, use a proper cron library
    
    const parts = cron.split(' ');
    
    // Handle simple cases
    if (cron === '* * * * *') {
      // Every minute
      return { interval: 60000 };
    } else if (cron === '0 * * * *') {
      // Every hour
      return { interval: 3600000 };
    } else if (cron === '0 0 * * *') {
      // Daily
      return { interval: 86400000 };
    }
    
    // For more complex expressions, would need proper parsing
    return {};
  }

  // Public methods for manual trigger management
  
  async registerTrigger(process: ProcessDefinition, trigger: ProcessTrigger): Promise<void> {
    if (!trigger.enabled) return;
    
    const key = `${process.id}:${trigger.id}`;
    this.activeTriggers.set(key, trigger);
    
    if (trigger.type === 'schedule' && trigger.config.cron) {
      // Use node-cron for scheduling
      if (!this.cronAdapter.validate(trigger.config.cron)) {
        throw new Error(`Invalid cron expression: ${trigger.config.cron}`);
      }
      
      const task = this.cronAdapter.schedule(trigger.config.cron, async () => {
        await this.executeTrigger(process.id, trigger.id);
      }, {
        scheduled: false
      });
      
      task.start();
      
      this.scheduledTriggers.set(key, {
        processId: process.id,
        triggerId: trigger.id,
        interval: task as any
      });
    }
  }
  
  async unregisterTrigger(processId: string, triggerId: string): Promise<void> {
    const key = `${processId}:${triggerId}`;
    this.activeTriggers.delete(key);
    
    const scheduled = this.scheduledTriggers.get(key);
    if (scheduled && scheduled.interval) {
      const task = scheduled.interval as any;
      if (task.stop) task.stop();
      if (task.destroy) task.destroy();
      this.scheduledTriggers.delete(key);
    }
  }
  
  getActiveTriggers(processId?: string): Array<ProcessTrigger & { processId: string }> {
    const triggers: Array<ProcessTrigger & { processId: string }> = [];
    
    for (const [key, trigger] of this.activeTriggers) {
      const [pid] = key.split(':');
      if (!processId || pid === processId) {
        triggers.push({ ...trigger, processId: pid });
      }
    }
    
    return triggers;
  }
  
  async executeTrigger(processId: string, triggerId: string, context?: Record<string, any>): Promise<any> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error('Process not found');
    }
    
    const trigger = process.triggers.find(t => t.id === triggerId);
    if (!trigger) {
      throw new Error('Trigger not found');
    }
    
    if (!trigger.enabled) {
      throw new Error('Trigger is disabled');
    }
    
    const execution = await this.engine.executeProcess(process, triggerId, context);
    
    // Record in history
    const key = `${processId}:${triggerId}`;
    if (!this.triggerHistory.has(key)) {
      this.triggerHistory.set(key, []);
    }
    
    const history = this.triggerHistory.get(key)!;
    history.unshift({
      triggerId,
      processId,
      executionId: execution.id,
      executedAt: new Date().toISOString(),
      status: execution.status === 'completed' ? 'success' : 'failed'
    });
    
    // Keep only last 100 entries
    if (history.length > 100) {
      history.splice(100);
    }
    
    return execution;
  }
  
  getTriggerHistory(processId: string, triggerId: string): TriggerExecution[] {
    const key = `${processId}:${triggerId}`;
    return this.triggerHistory.get(key) || [];
  }
  
  async updateTrigger(processId: string, triggerId: string, updates: Partial<ProcessTrigger>): Promise<void> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error('Process not found');
    }
    
    const trigger = process.triggers.find(t => t.id === triggerId);
    if (!trigger) {
      throw new Error('Trigger not found');
    }
    
    // If disabling, unregister first
    if (updates.enabled === false && trigger.enabled) {
      await this.unregisterTrigger(processId, triggerId);
    }
    
    // Update the trigger
    Object.assign(trigger, updates);
    
    // Save the updated process
    await this.store.saveProcess(process);
    
    // If enabling or if already enabled and config changed, re-register
    if (trigger.enabled) {
      // Unregister first to clean up old configuration
      await this.unregisterTrigger(processId, triggerId);
      // Re-register with new configuration
      await this.registerTrigger(process, trigger);
    }
  }
  
  async handleEvent(eventType: string, eventData: any): Promise<void> {
    for (const [key, trigger] of this.activeTriggers) {
      if (trigger.type === 'event' && trigger.config.event === eventType) {
        const [processId] = key.split(':');
        const process = await this.store.getProcess(processId);
        
        if (process) {
          await this.executeTrigger(processId, trigger.id, {
            event: eventType,
            eventData
          });
        }
      }
    }
  }
  
  stopAll(): void {
    this.stop();
    
    for (const scheduled of this.scheduledTriggers.values()) {
      if (scheduled.interval) {
        const task = scheduled.interval as any;
        if (task.stop) task.stop();
        if (task.destroy) task.destroy();
      }
    }
    
    this.scheduledTriggers.clear();
    this.activeTriggers.clear();
  }
  
  async triggerProcess(processId: string, variables?: Record<string, any>): Promise<void> {
    const process = await this.store.getProcess(processId);
    if (!process) {
      throw new Error('Process not found');
    }
    
    // Find manual trigger or use first trigger
    const trigger = process.triggers.find(t => t.type === 'manual') || process.triggers[0];
    
    if (!trigger) {
      // Create a temporary manual trigger
      const manualTrigger: ProcessTrigger = {
        id: 'manual-temp',
        type: 'manual',
        name: 'Manual Execution',
        enabled: true,
        config: {}
      };
      
      await this.engine.executeProcess(process, manualTrigger.id, variables);
    } else {
      await this.engine.executeProcess(process, trigger.id, variables);
    }
  }

  async enableTrigger(processId: string, triggerId: string): Promise<void> {
    const process = await this.store.getProcess(processId);
    if (!process) return;
    
    const trigger = process.triggers.find(t => t.id === triggerId);
    if (trigger && !trigger.enabled) {
      trigger.enabled = true;
      await this.store.saveProcess(process);
      await this.registerTrigger(process, trigger);
    }
  }

  async disableTrigger(processId: string, triggerId: string): Promise<void> {
    const process = await this.store.getProcess(processId);
    if (!process) return;
    
    const trigger = process.triggers.find(t => t.id === triggerId);
    if (trigger && trigger.enabled) {
      trigger.enabled = false;
      await this.store.saveProcess(process);
      
      // Remove from scheduled triggers
      const key = `${processId}:${triggerId}`;
      const scheduled = this.scheduledTriggers.get(key);
      
      if (scheduled && scheduled.interval) {
        clearInterval(scheduled.interval);
        this.scheduledTriggers.delete(key);
      }
    }
  }
}