import Bull, { Job, Queue, QueueScheduler, JobOptions } from 'bull';
import { EventEmitter } from 'events';
import { stateMachineService } from './stateMachine';
import { webhookService } from './webhookService';
import { auditLogger } from './auditLogger';
import { gitEventHandler } from './gitEventHandler';
import { getAuthenticatedClient } from '../../scripts/utils/directus-client';

// Import Directus SDK functions
const directusSdk = require('@directus/sdk');
const readItem = directusSdk.readItem as any;
const updateItem = directusSdk.updateItem as any;

/**
 * Job Types
 */
export enum JobType {
  STATUS_TRANSITION = 'status_transition',
  WEBHOOK_DELIVERY = 'webhook_delivery',
  BATCH_UPDATE = 'batch_update',
  SCHEDULED_TRANSITION = 'scheduled_transition',
  RECURRING_CHECK = 'recurring_check',
  CLEANUP = 'cleanup',
  NOTIFICATION = 'notification',
  SYNC = 'sync',
  EXPORT = 'export',
  ARCHIVE = 'archive'
}

/**
 * Job Priority Levels
 */
export enum JobPriority {
  CRITICAL = 1,
  HIGH = 5,
  NORMAL = 10,
  LOW = 20
}

/**
 * Job Data Interface
 */
export interface JobData {
  type: JobType;
  taskId?: string;
  payload: any;
  metadata?: Record<string, any>;
  retryOnFail?: boolean;
  maxRetries?: number;
}

/**
 * Job Result Interface
 */
export interface JobResult {
  success: boolean;
  data?: any;
  error?: string;
  duration?: number;
  timestamp: string;
}

/**
 * Queue Statistics
 */
export interface QueueStatistics {
  waiting: number;
  active: number;
  completed: number;
  failed: number;
  delayed: number;
  paused: boolean;
  jobCounts: Record<string, number>;
  processingRate: number;
  averageProcessingTime: number;
}

/**
 * Job Queue Service
 * Manages asynchronous job processing using Bull
 */
export class JobQueueService extends EventEmitter {
  private queues: Map<string, Queue> = new Map();
  private schedulers: Map<string, QueueScheduler> = new Map();
  private client: any;
  private redisConfig: any;
  private isProcessing: boolean = false;

  constructor() {
    super();
    this.redisConfig = {
      host: process.env.REDIS_HOST || 'localhost',
      port: parseInt(process.env.REDIS_PORT || '6379'),
      password: process.env.REDIS_PASSWORD
    };
    this.initializeClient();
    this.initializeQueues();
  }

  private async initializeClient() {
    this.client = await getAuthenticatedClient();
  }

  /**
   * Initialize job queues
   */
  private initializeQueues(): void {
    // Main workflow queue
    this.createQueue('workflow', {
      defaultJobOptions: {
        removeOnComplete: 100,
        removeOnFail: 50,
        attempts: 3,
        backoff: {
          type: 'exponential',
          delay: 2000
        }
      }
    });

    // Scheduled jobs queue
    this.createQueue('scheduled', {
      defaultJobOptions: {
        removeOnComplete: true,
        removeOnFail: false
      }
    });

    // Notification queue
    this.createQueue('notification', {
      defaultJobOptions: {
        removeOnComplete: true,
        attempts: 5,
        backoff: {
          type: 'fixed',
          delay: 5000
        }
      }
    });

    // Maintenance queue
    this.createQueue('maintenance', {
      defaultJobOptions: {
        removeOnComplete: true,
        removeOnFail: false
      }
    });

    console.log('Job queues initialized');
  }

  /**
   * Create a new queue
   */
  private createQueue(name: string, options?: any): Queue {
    // Create queue
    const queue = new Bull(name, {
      redis: this.redisConfig,
      ...options
    });

    // Create scheduler for delayed/repeated jobs
    const scheduler = new QueueScheduler(name, {
      redis: this.redisConfig
    });

    // Store references
    this.queues.set(name, queue);
    this.schedulers.set(name, scheduler);

    // Set up event handlers
    this.setupQueueEventHandlers(queue, name);

    // Set up processor
    queue.process(async (job) => {
      return await this.processJob(job, name);
    });

    return queue;
  }

  /**
   * Set up queue event handlers
   */
  private setupQueueEventHandlers(queue: Queue, queueName: string): void {
    queue.on('completed', (job, result) => {
      console.log(`Job ${job.id} completed in ${queueName} queue`);
      this.emit('jobCompleted', { queue: queueName, job, result });
      
      // Log to audit
      auditLogger.log({
        entity_type: 'system',
        entity_id: `job-${job.id}`,
        action: 'job_completed',
        actor_type: 'system',
        details: {
          queue: queueName,
          jobType: job.data.type,
          duration: job.finishedOn ? job.finishedOn - job.processedOn! : 0,
          result
        },
        severity: 'info'
      });
    });

    queue.on('failed', (job, err) => {
      console.error(`Job ${job.id} failed in ${queueName} queue:`, err);
      this.emit('jobFailed', { queue: queueName, job, error: err });
      
      // Log error
      auditLogger.logError('system', `job-${job.id}`, err, {
        queue: queueName,
        jobType: job.data.type,
        attempts: job.attemptsMade
      });
    });

    queue.on('stalled', (job) => {
      console.warn(`Job ${job.id} stalled in ${queueName} queue`);
      this.emit('jobStalled', { queue: queueName, job });
    });

    queue.on('progress', (job, progress) => {
      this.emit('jobProgress', { queue: queueName, job, progress });
    });
  }

  /**
   * Process a job
   */
  private async processJob(job: Job<JobData>, queueName: string): Promise<JobResult> {
    const startTime = Date.now();
    const { type, taskId, payload, metadata } = job.data;

    try {
      let result: any;

      switch (type) {
        case JobType.STATUS_TRANSITION:
          result = await this.processStatusTransition(taskId!, payload);
          break;
        
        case JobType.WEBHOOK_DELIVERY:
          result = await this.processWebhookDelivery(payload);
          break;
        
        case JobType.BATCH_UPDATE:
          result = await this.processBatchUpdate(payload);
          break;
        
        case JobType.SCHEDULED_TRANSITION:
          result = await this.processScheduledTransition(taskId!, payload);
          break;
        
        case JobType.RECURRING_CHECK:
          result = await this.processRecurringCheck(payload);
          break;
        
        case JobType.CLEANUP:
          result = await this.processCleanup(payload);
          break;
        
        case JobType.NOTIFICATION:
          result = await this.processNotification(payload);
          break;
        
        case JobType.SYNC:
          result = await this.processSync(payload);
          break;
        
        case JobType.EXPORT:
          result = await this.processExport(payload);
          break;
        
        case JobType.ARCHIVE:
          result = await this.processArchive(payload);
          break;
        
        default:
          throw new Error(`Unknown job type: ${type}`);
      }

      return {
        success: true,
        data: result,
        duration: Date.now() - startTime,
        timestamp: new Date().toISOString()
      };
    } catch (error: any) {
      return {
        success: false,
        error: error.message,
        duration: Date.now() - startTime,
        timestamp: new Date().toISOString()
      };
    }
  }

  /**
   * Process status transition job
   */
  private async processStatusTransition(taskId: string, payload: any): Promise<any> {
    const { event, userId, reason } = payload;
    
    // Execute transition
    const newState = await stateMachineService.transition(taskId, {
      type: event,
      ...payload
    });

    // Trigger webhooks
    await webhookService.triggerWebhooks(`task.${event.toLowerCase()}`, taskId, {
      previousStatus: payload.previousStatus,
      currentStatus: newState.value,
      task: { id: taskId },
      metadata: { userId, reason }
    });

    return { taskId, newState: newState.value };
  }

  /**
   * Process webhook delivery job
   */
  private async processWebhookDelivery(payload: any): Promise<any> {
    // This is handled by webhook service's own queue
    // But we can add additional processing here if needed
    return { processed: true };
  }

  /**
   * Process batch update job
   */
  private async processBatchUpdate(payload: any): Promise<any> {
    const { taskIds, updates } = payload;
    const results = [];

    for (const taskId of taskIds) {
      try {
        await this.client.request(
          updateItem('tasks', taskId, {
            ...updates,
            updated_at: new Date().toISOString()
          })
        );
        results.push({ taskId, success: true });
      } catch (error: any) {
        results.push({ taskId, success: false, error: error.message });
      }
    }

    return { processed: taskIds.length, results };
  }

  /**
   * Process scheduled transition job
   */
  private async processScheduledTransition(taskId: string, payload: any): Promise<any> {
    // Check if conditions are met
    const task = await this.client.request(readItem('tasks', taskId));
    
    if (this.checkTransitionConditions(task, payload.conditions)) {
      return await this.processStatusTransition(taskId, payload);
    }

    return { skipped: true, reason: 'Conditions not met' };
  }

  /**
   * Process recurring check job
   */
  private async processRecurringCheck(payload: any): Promise<any> {
    const { checkType, parameters } = payload;
    
    switch (checkType) {
      case 'stale_tasks':
        return await this.checkStaleTasks(parameters);
      case 'overdue_tasks':
        return await this.checkOverdueTasks(parameters);
      case 'blocked_tasks':
        return await this.checkBlockedTasks(parameters);
      default:
        throw new Error(`Unknown check type: ${checkType}`);
    }
  }

  /**
   * Process cleanup job
   */
  private async processCleanup(payload: any): Promise<any> {
    const { cleanupType, parameters } = payload;
    
    switch (cleanupType) {
      case 'old_logs':
        return await auditLogger.archiveOldLogs(parameters.daysToKeep);
      case 'completed_jobs':
        return await this.cleanCompletedJobs(parameters);
      case 'orphaned_tasks':
        return await this.cleanOrphanedTasks(parameters);
      default:
        throw new Error(`Unknown cleanup type: ${cleanupType}`);
    }
  }

  /**
   * Process notification job
   */
  private async processNotification(payload: any): Promise<any> {
    // Implement notification logic
    console.log('Processing notification:', payload);
    return { sent: true };
  }

  /**
   * Process sync job
   */
  private async processSync(payload: any): Promise<any> {
    // Implement sync logic
    console.log('Processing sync:', payload);
    return { synced: true };
  }

  /**
   * Process export job
   */
  private async processExport(payload: any): Promise<any> {
    const { exportType, format, options } = payload;
    
    switch (exportType) {
      case 'audit_logs':
        return await auditLogger.exportLogs(options, format);
      default:
        throw new Error(`Unknown export type: ${exportType}`);
    }
  }

  /**
   * Process archive job
   */
  private async processArchive(payload: any): Promise<any> {
    // Implement archive logic
    console.log('Processing archive:', payload);
    return { archived: true };
  }

  /**
   * Add job to queue
   */
  async addJob(
    queueName: string,
    jobData: JobData,
    options?: JobOptions
  ): Promise<Job> {
    const queue = this.queues.get(queueName);
    if (!queue) {
      throw new Error(`Queue ${queueName} not found`);
    }

    const job = await queue.add(jobData, options);
    
    console.log(`Added job ${job.id} to ${queueName} queue`);
    
    return job;
  }

  /**
   * Schedule a job
   */
  async scheduleJob(
    queueName: string,
    jobData: JobData,
    delay: number | Date,
    options?: JobOptions
  ): Promise<Job> {
    const delayMs = delay instanceof Date 
      ? delay.getTime() - Date.now()
      : delay;

    return await this.addJob(queueName, jobData, {
      ...options,
      delay: delayMs
    });
  }

  /**
   * Add recurring job
   */
  async addRecurringJob(
    queueName: string,
    jobData: JobData,
    cronExpression: string,
    options?: JobOptions
  ): Promise<Job> {
    const queue = this.queues.get(queueName);
    if (!queue) {
      throw new Error(`Queue ${queueName} not found`);
    }

    const job = await queue.add(jobData, {
      ...options,
      repeat: {
        cron: cronExpression
      }
    });

    console.log(`Added recurring job ${job.id} to ${queueName} queue`);
    
    return job;
  }

  /**
   * Get queue statistics
   */
  async getQueueStatistics(queueName: string): Promise<QueueStatistics> {
    const queue = this.queues.get(queueName);
    if (!queue) {
      throw new Error(`Queue ${queueName} not found`);
    }

    const [waiting, active, completed, failed, delayed] = await Promise.all([
      queue.getWaitingCount(),
      queue.getActiveCount(),
      queue.getCompletedCount(),
      queue.getFailedCount(),
      queue.getDelayedCount()
    ]);

    const isPaused = await queue.isPaused();

    // Calculate processing metrics
    const completedJobs = await queue.getCompleted(0, 100);
    let totalProcessingTime = 0;
    completedJobs.forEach(job => {
      if (job.finishedOn && job.processedOn) {
        totalProcessingTime += job.finishedOn - job.processedOn;
      }
    });

    const averageProcessingTime = completedJobs.length > 0
      ? totalProcessingTime / completedJobs.length
      : 0;

    return {
      waiting,
      active,
      completed,
      failed,
      delayed,
      paused: isPaused,
      jobCounts: {},
      processingRate: completed / (Date.now() / 1000 / 60), // jobs per minute
      averageProcessingTime
    };
  }

  /**
   * Pause queue
   */
  async pauseQueue(queueName: string): Promise<void> {
    const queue = this.queues.get(queueName);
    if (!queue) {
      throw new Error(`Queue ${queueName} not found`);
    }

    await queue.pause();
    console.log(`Queue ${queueName} paused`);
  }

  /**
   * Resume queue
   */
  async resumeQueue(queueName: string): Promise<void> {
    const queue = this.queues.get(queueName);
    if (!queue) {
      throw new Error(`Queue ${queueName} not found`);
    }

    await queue.resume();
    console.log(`Queue ${queueName} resumed`);
  }

  /**
   * Clear queue
   */
  async clearQueue(queueName: string): Promise<void> {
    const queue = this.queues.get(queueName);
    if (!queue) {
      throw new Error(`Queue ${queueName} not found`);
    }

    await queue.empty();
    console.log(`Queue ${queueName} cleared`);
  }

  /**
   * Check transition conditions
   */
  private checkTransitionConditions(task: any, conditions: any): boolean {
    // Implement condition checking logic
    return true;
  }

  /**
   * Check stale tasks
   */
  private async checkStaleTasks(parameters: any): Promise<any> {
    // Implement stale task checking
    return { checked: true };
  }

  /**
   * Check overdue tasks
   */
  private async checkOverdueTasks(parameters: any): Promise<any> {
    // Implement overdue task checking
    return { checked: true };
  }

  /**
   * Check blocked tasks
   */
  private async checkBlockedTasks(parameters: any): Promise<any> {
    // Implement blocked task checking
    return { checked: true };
  }

  /**
   * Clean completed jobs
   */
  private async cleanCompletedJobs(parameters: any): Promise<any> {
    let totalCleaned = 0;
    
    for (const [name, queue] of this.queues) {
      const cleaned = await queue.clean(
        parameters.gracePeriod || 3600000, // 1 hour default
        'completed'
      );
      totalCleaned += cleaned.length;
    }

    return { cleaned: totalCleaned };
  }

  /**
   * Clean orphaned tasks
   */
  private async cleanOrphanedTasks(parameters: any): Promise<any> {
    // Implement orphaned task cleanup
    return { cleaned: 0 };
  }

  /**
   * Set up default recurring jobs
   */
  async setupDefaultRecurringJobs(): Promise<void> {
    // Check stale tasks every hour
    await this.addRecurringJob('maintenance', {
      type: JobType.RECURRING_CHECK,
      payload: {
        checkType: 'stale_tasks',
        parameters: { staleDays: 7 }
      }
    }, '0 * * * *');

    // Clean old logs daily at 2 AM
    await this.addRecurringJob('maintenance', {
      type: JobType.CLEANUP,
      payload: {
        cleanupType: 'old_logs',
        parameters: { daysToKeep: 90 }
      }
    }, '0 2 * * *');

    // Clean completed jobs every 6 hours
    await this.addRecurringJob('maintenance', {
      type: JobType.CLEANUP,
      payload: {
        cleanupType: 'completed_jobs',
        parameters: { gracePeriod: 86400000 } // 24 hours
      }
    }, '0 */6 * * *');

    console.log('Default recurring jobs set up');
  }

  /**
   * Clean up service
   */
  async cleanup(): Promise<void> {
    // Close all queues
    for (const [name, queue] of this.queues) {
      await queue.close();
    }

    // Close all schedulers
    for (const [name, scheduler] of this.schedulers) {
      await scheduler.close();
    }

    this.queues.clear();
    this.schedulers.clear();
    this.removeAllListeners();

    console.log('Job queue service cleaned up');
  }
}

// Export singleton instance
export const jobQueueService = new JobQueueService();