"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.jobQueueService = exports.JobQueueService = exports.JobPriority = exports.JobType = void 0;
const bull_1 = __importStar(require("bull"));
const events_1 = require("events");
const stateMachine_1 = require("./stateMachine");
const webhookService_1 = require("./webhookService");
const auditLogger_1 = require("./auditLogger");
const directus_client_1 = require("../../scripts/utils/directus-client");
// Import Directus SDK functions
const directusSdk = require('@directus/sdk');
const readItem = directusSdk.readItem;
const updateItem = directusSdk.updateItem;
/**
 * Job Types
 */
var JobType;
(function (JobType) {
    JobType["STATUS_TRANSITION"] = "status_transition";
    JobType["WEBHOOK_DELIVERY"] = "webhook_delivery";
    JobType["BATCH_UPDATE"] = "batch_update";
    JobType["SCHEDULED_TRANSITION"] = "scheduled_transition";
    JobType["RECURRING_CHECK"] = "recurring_check";
    JobType["CLEANUP"] = "cleanup";
    JobType["NOTIFICATION"] = "notification";
    JobType["SYNC"] = "sync";
    JobType["EXPORT"] = "export";
    JobType["ARCHIVE"] = "archive";
})(JobType || (exports.JobType = JobType = {}));
/**
 * Job Priority Levels
 */
var JobPriority;
(function (JobPriority) {
    JobPriority[JobPriority["CRITICAL"] = 1] = "CRITICAL";
    JobPriority[JobPriority["HIGH"] = 5] = "HIGH";
    JobPriority[JobPriority["NORMAL"] = 10] = "NORMAL";
    JobPriority[JobPriority["LOW"] = 20] = "LOW";
})(JobPriority || (exports.JobPriority = JobPriority = {}));
/**
 * Job Queue Service
 * Manages asynchronous job processing using Bull
 */
class JobQueueService extends events_1.EventEmitter {
    constructor() {
        super();
        this.queues = new Map();
        this.schedulers = new Map();
        this.isProcessing = false;
        this.redisConfig = {
            host: process.env.REDIS_HOST || 'localhost',
            port: parseInt(process.env.REDIS_PORT || '6379'),
            password: process.env.REDIS_PASSWORD
        };
        this.initializeClient();
        this.initializeQueues();
    }
    async initializeClient() {
        this.client = await (0, directus_client_1.getAuthenticatedClient)();
    }
    /**
     * Initialize job queues
     */
    initializeQueues() {
        // 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
     */
    createQueue(name, options) {
        // Create queue
        const queue = new bull_1.default(name, {
            redis: this.redisConfig,
            ...options
        });
        // Create scheduler for delayed/repeated jobs
        const scheduler = new bull_1.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
     */
    setupQueueEventHandlers(queue, queueName) {
        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_1.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_1.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
     */
    async processJob(job, queueName) {
        const startTime = Date.now();
        const { type, taskId, payload, metadata } = job.data;
        try {
            let result;
            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) {
            return {
                success: false,
                error: error.message,
                duration: Date.now() - startTime,
                timestamp: new Date().toISOString()
            };
        }
    }
    /**
     * Process status transition job
     */
    async processStatusTransition(taskId, payload) {
        const { event, userId, reason } = payload;
        // Execute transition
        const newState = await stateMachine_1.stateMachineService.transition(taskId, {
            type: event,
            ...payload
        });
        // Trigger webhooks
        await webhookService_1.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
     */
    async processWebhookDelivery(payload) {
        // 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
     */
    async processBatchUpdate(payload) {
        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) {
                results.push({ taskId, success: false, error: error.message });
            }
        }
        return { processed: taskIds.length, results };
    }
    /**
     * Process scheduled transition job
     */
    async processScheduledTransition(taskId, payload) {
        // 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
     */
    async processRecurringCheck(payload) {
        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
     */
    async processCleanup(payload) {
        const { cleanupType, parameters } = payload;
        switch (cleanupType) {
            case 'old_logs':
                return await auditLogger_1.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
     */
    async processNotification(payload) {
        // Implement notification logic
        console.log('Processing notification:', payload);
        return { sent: true };
    }
    /**
     * Process sync job
     */
    async processSync(payload) {
        // Implement sync logic
        console.log('Processing sync:', payload);
        return { synced: true };
    }
    /**
     * Process export job
     */
    async processExport(payload) {
        const { exportType, format, options } = payload;
        switch (exportType) {
            case 'audit_logs':
                return await auditLogger_1.auditLogger.exportLogs(options, format);
            default:
                throw new Error(`Unknown export type: ${exportType}`);
        }
    }
    /**
     * Process archive job
     */
    async processArchive(payload) {
        // Implement archive logic
        console.log('Processing archive:', payload);
        return { archived: true };
    }
    /**
     * Add job to queue
     */
    async addJob(queueName, jobData, options) {
        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, jobData, delay, options) {
        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, jobData, cronExpression, options) {
        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) {
        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) {
        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) {
        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) {
        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
     */
    checkTransitionConditions(task, conditions) {
        // Implement condition checking logic
        return true;
    }
    /**
     * Check stale tasks
     */
    async checkStaleTasks(parameters) {
        // Implement stale task checking
        return { checked: true };
    }
    /**
     * Check overdue tasks
     */
    async checkOverdueTasks(parameters) {
        // Implement overdue task checking
        return { checked: true };
    }
    /**
     * Check blocked tasks
     */
    async checkBlockedTasks(parameters) {
        // Implement blocked task checking
        return { checked: true };
    }
    /**
     * Clean completed jobs
     */
    async cleanCompletedJobs(parameters) {
        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
     */
    async cleanOrphanedTasks(parameters) {
        // Implement orphaned task cleanup
        return { cleaned: 0 };
    }
    /**
     * Set up default recurring jobs
     */
    async setupDefaultRecurringJobs() {
        // 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() {
        // 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');
    }
}
exports.JobQueueService = JobQueueService;
// Export singleton instance
exports.jobQueueService = new JobQueueService();
//# sourceMappingURL=jobQueue.js.map