/**
 * Queue factory and setup for run execution
 * Creates BullMQ queues and workers for processing run execution jobs
 */

import { Queue, Worker, QueueEvents } from 'bullmq';
import { getRedisClient } from '../db/redis.js';
import { queueConfig, RunExecutionJobData } from './config.js';
import { createLogger } from '@4runr/shared';
import { processRunExecutionJob } from './processor.js';

const logger = createLogger('Gateway:Queue');

let runExecutionQueue: Queue<RunExecutionJobData> | null = null;
let runExecutionWorker: Worker<RunExecutionJobData> | null = null;
let runExecutionQueueEvents: QueueEvents | null = null;

/**
 * Initialize the run execution queue
 * Returns null if Redis is not available
 */
export async function initializeQueue(): Promise<Queue<RunExecutionJobData> | null> {
  if (runExecutionQueue) {
    return runExecutionQueue;
  }

  const redis = await getRedisClient();
  if (!redis) {
    logger.warn('Redis not available, queue system disabled');
    return null;
  }

  try {
    // Create queue
    runExecutionQueue = new Queue<RunExecutionJobData>(queueConfig.name, {
      connection: redis,
      defaultJobOptions: {
        attempts: queueConfig.attempts,
        backoff: queueConfig.backoff,
        removeOnComplete: queueConfig.removeOnComplete,
        removeOnFail: queueConfig.removeOnFail,
      },
    });

    // Create worker to process jobs
    runExecutionWorker = new Worker<RunExecutionJobData>(
      queueConfig.name,
      async (job) => {
        return await processRunExecutionJob(job);
      },
      {
        connection: redis,
        concurrency: 10, // Process up to 10 jobs concurrently
        removeOnComplete: queueConfig.removeOnComplete,
        removeOnFail: queueConfig.removeOnFail,
      }
    );

    // Setup worker event handlers
    runExecutionWorker.on('completed', (job) => {
      logger.info('Job completed', { 
        jobId: job.id, 
        runId: job.data.runId 
      });
    });

    runExecutionWorker.on('failed', (job, err) => {
      logger.error('Job failed', { 
        jobId: job?.id, 
        runId: job?.data.runId,
        error: err.message,
        attemptsMade: job?.attemptsMade,
      });
    });

    runExecutionWorker.on('active', (job) => {
      logger.debug('Job active', { 
        jobId: job.id, 
        runId: job.data.runId 
      });
    });

    runExecutionWorker.on('error', (err) => {
      logger.error('Worker error', { error: err.message });
    });

    // Create queue events for monitoring
    runExecutionQueueEvents = new QueueEvents(queueConfig.name, {
      connection: redis,
    });

    runExecutionQueueEvents.on('waiting', ({ jobId }) => {
      logger.debug('Job waiting', { jobId });
    });

    runExecutionQueueEvents.on('active', ({ jobId }) => {
      logger.debug('Job active', { jobId });
    });

    runExecutionQueueEvents.on('completed', ({ jobId }) => {
      logger.debug('Job completed', { jobId });
    });

    runExecutionQueueEvents.on('failed', ({ jobId, failedReason }) => {
      logger.warn('Job failed', { jobId, failedReason });
    });

    logger.info('Queue system initialized', { 
      queueName: queueConfig.name,
      concurrency: 10,
    });

    return runExecutionQueue;
  } catch (error) {
    logger.error('Failed to initialize queue', { 
      error: error instanceof Error ? error.message : String(error),
    });
    return null;
  }
}

/**
 * Get the run execution queue instance
 * Returns null if queue is not initialized or Redis is not available
 */
export function getQueue(): Queue<RunExecutionJobData> | null {
  return runExecutionQueue;
}

/**
 * Add a run execution job to the queue
 */
export async function queueRunExecution(
  runId: string,
  priority: 'high' | 'normal' | 'low' = 'normal'
): Promise<void> {
  const queue = getQueue();
  if (!queue) {
    logger.warn('Queue not available, cannot queue run execution', { runId });
    throw new Error('Queue system not available');
  }

  const jobData: RunExecutionJobData = {
    runId,
    priority,
  };

  const priorityValue = queueConfig.priorities[priority];

  await queue.add('run-execution', jobData, {
    priority: priorityValue,
    jobId: `run-${runId}`, // Use runId as jobId to prevent duplicates
  });

  logger.info('Run execution queued', { runId, priority });
}

/**
 * Remove a run execution job from the queue
 * Used when a run is canceled/killed
 */
export async function removeRunExecutionJob(runId: string): Promise<void> {
  const queue = getQueue();
  if (!queue) {
    return;
  }

  const job = await queue.getJob(`run-${runId}`);
  if (job) {
    await job.remove();
    logger.info('Run execution job removed', { runId });
  }
}

/**
 * Get job status for a run
 */
export async function getRunJobStatus(runId: string): Promise<{
  state: 'waiting' | 'active' | 'completed' | 'failed' | 'delayed' | null;
  progress?: number;
  attemptsMade?: number;
} | null> {
  const queue = getQueue();
  if (!queue) {
    return null;
  }

  const job = await queue.getJob(`run-${runId}`);
  if (!job) {
    return null;
  }

  const state = await job.getState();
  
  // job.progress can be number or object, convert to number
  // Only include progress if it's a number (for exactOptionalPropertyTypes)
  const progress = typeof job.progress === 'number' ? job.progress : undefined;

  return {
    state: state as 'waiting' | 'active' | 'completed' | 'failed' | 'delayed' | null,
    ...(progress !== undefined && { progress }),
    attemptsMade: job.attemptsMade,
  };
}

/**
 * Shutdown queue system gracefully
 */
export async function shutdownQueue(): Promise<void> {
  logger.info('Shutting down queue system');

  if (runExecutionWorker) {
    await runExecutionWorker.close();
    runExecutionWorker = null;
  }

  if (runExecutionQueueEvents) {
    await runExecutionQueueEvents.close();
    runExecutionQueueEvents = null;
  }

  if (runExecutionQueue) {
    await runExecutionQueue.close();
    runExecutionQueue = null;
  }

  logger.info('Queue system shut down');
}

