"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SQSDispatcher = void 0;
const logger_1 = require("../utils/logger");
const helpers_1 = require("../prophet/helpers");
const helpers_2 = require("../utils/helpers");
class SQSDispatcher {
    sqsService;
    config;
    prometheus;
    processSQSEventDataMessage;
    startedQueues;
    dispatchesPerMinute;
    unhlandledDispatchesPerMinute;
    errorDispatchesPerMinute;
    duplicatedDispatchesPerMinute;
    handledDispatchesPerMinute;
    constructor(sqsService, config, prometheus, processSQSEventDataMessage) {
        this.sqsService = sqsService;
        this.config = config;
        this.prometheus = prometheus;
        this.processSQSEventDataMessage = processSQSEventDataMessage;
        this.startedQueues = [];
        this.unhlandledDispatchesPerMinute = {};
        this.dispatchesPerMinute = {};
        this.errorDispatchesPerMinute = {};
        this.duplicatedDispatchesPerMinute = {};
        this.handledDispatchesPerMinute = {};
    }
    async reportStatsPerQueue(queueName) {
        logger_1.logger.info({ queueName }, '🚀 Starting SQS dispatcher stats reporting...');
        while (true) {
            await (0, helpers_2.sleep)(60);
            const dispatches = this.dispatchesPerMinute[queueName] || 0;
            const handled = this.handledDispatchesPerMinute[queueName] || 0;
            const error = this.errorDispatchesPerMinute[queueName] || 0;
            const duplicated = this.duplicatedDispatchesPerMinute[queueName] || 0;
            const unhandled = this.unhlandledDispatchesPerMinute[queueName] || 0;
            const sqsDirectionLabel = 'dispatch';
            // Report stats to Prometheus
            this.prometheus.eventQueueProcessingHealthcheckRates.set({
                queueName,
                sqsDirectionLabel,
                label: 'handled',
            }, handled);
            this.prometheus.eventQueueProcessingHealthcheckRates.set({
                queueName,
                sqsDirectionLabel,
                label: 'error',
            }, error);
            this.prometheus.eventQueueProcessingHealthcheckRates.set({
                queueName,
                sqsDirectionLabel,
                label: 'dispatches',
            }, dispatches);
            this.prometheus.eventQueueProcessingHealthcheckRates.set({
                queueName,
                sqsDirectionLabel,
                label: 'duplicated',
            }, duplicated);
            this.prometheus.eventQueueProcessingHealthcheckRates.set({
                queueName,
                sqsDirectionLabel,
                label: 'unhandled',
            }, unhandled);
            logger_1.logger.info({
                queueName,
                dispatches,
                handled,
                error,
                sqsDirectionLabel,
                duplicated,
                unhandled,
            }, '📊 SQS dispatcher stats');
            this.resetQueueRateCounters(queueName);
        }
    }
    reportStats() {
        // For each queue name in startedQueues
        for (const queueName of this.startedQueues) {
            void this.reportStatsPerQueue(queueName);
        }
    }
    resetQueueRateCounters(queueName) {
        this.handledDispatchesPerMinute[queueName] = 0;
        this.unhlandledDispatchesPerMinute[queueName] = 0;
        this.errorDispatchesPerMinute[queueName] = 0;
        this.dispatchesPerMinute[queueName] = 0;
        this.duplicatedDispatchesPerMinute[queueName] = 0;
    }
    async startConsumingSQSMessages(processIndex, queueName) {
        if (this.startedQueues.includes(queueName)) {
            logger_1.logger.error(`SQS Queue ${queueName} already started, skipping...`);
            return;
        }
        this.resetQueueRateCounters(queueName);
        // const queueName = this.config.aws.sqs.eventListenerQueueDLQ
        const childLogger = logger_1.logger.child({ processIndex, queueName });
        while (true) {
            try {
                const messages = await this.sqsService.retrieveMessages(queueName, childLogger);
                const retrieveTimeSec = Date.now() / 1000;
                if (!messages || messages.length === 0) {
                    childLogger.debug('📦 No messages received...');
                    continue;
                }
                // Sort events by priority
                if (this.config.prophet.sortSQSEventsByPriority) {
                    messages.sort((a, b) => (0, helpers_1.getEventPriority)(a) - (0, helpers_1.getEventPriority)(b));
                }
                // Parallel processing
                await Promise.all(messages.map((message) => this.handleSingleSQSMessage(message, queueName, childLogger, retrieveTimeSec)));
            }
            catch (error) {
                childLogger.error({ error: error.message }, '🚨 Error retrieving messages from SQS DLQ');
            }
        }
    }
    async handleSingleSQSMessage(message, queueName, childLogger, retrieveTimeSec) {
        const startTimeSec = Date.now() / 1000;
        let eventName, networkId;
        try {
            if (!message.Body) {
                childLogger.error('🚨 Event Processing: Empty message body');
                return;
            }
            if (!message.ReceiptHandle) {
                childLogger.error('🚨 Event Processing: Empty message receipt handle');
                return;
            }
            const messageData = JSON.parse(message.Body);
            if (!messageData) {
                childLogger.error({ message: message.Body }, '🚨 Event Processing: Invalid message data');
                return;
            }
            const maybeMessageAttributesNetworkId = message.MessageAttributes && message.MessageAttributes.networkId
                ? message.MessageAttributes.networkId.StringValue
                : undefined;
            const maybeMessageAttributesEventName = message.MessageAttributes && message.MessageAttributes.eventName
                ? message.MessageAttributes.eventName.StringValue
                : undefined;
            const maybeMessageAttributesOrderId = message.MessageAttributes && message.MessageAttributes.orderId
                ? message.MessageAttributes.orderId.StringValue
                : undefined;
            const maybeMessageAttributesSourceNetwork = message.MessageAttributes && message.MessageAttributes.sourceNetwork
                ? message.MessageAttributes.sourceNetwork.StringValue
                : undefined;
            const maybeMessageAttributesDestinationNetwork = message.MessageAttributes && message.MessageAttributes.destinationNetwork
                ? message.MessageAttributes.destinationNetwork.StringValue
                : undefined;
            const maybeMessageAttributesOriginApp = message.MessageAttributes && message.MessageAttributes.originApp
                ? message.MessageAttributes.originApp.StringValue
                : undefined;
            networkId = messageData.networkId || maybeMessageAttributesNetworkId;
            eventName = messageData.eventName || maybeMessageAttributesEventName;
            const orderId = messageData.orderId || maybeMessageAttributesOrderId;
            const sourceNetwork = messageData.sourceNetwork || maybeMessageAttributesSourceNetwork;
            const destinationNetwork = messageData.destinationNetwork || maybeMessageAttributesDestinationNetwork;
            const originApp = messageData.originApp || maybeMessageAttributesOriginApp;
            const messageChildLogger = childLogger.child({
                eventName,
                networkId,
                sourceNetwork,
                destinationNetwork,
                id: orderId,
                originApp,
                queueName,
            });
            if (!orderId || !eventName || !networkId || !sourceNetwork || !destinationNetwork) {
                messageChildLogger.error('🚨 Event Processing: Missing required attributes in MessageAttributes');
                return;
            }
            (0, logger_1.batchLogger)({ id: orderId }, `📦 Processing ${eventName} SQS message originated from ${originApp}`);
            if (['RemoteOrderCreated', 'RevertOrderApproved', 'NewOrderAttested'].includes(eventName)) {
                (0, logger_1.batchLogger)({ id: orderId }, `📦 Processing ${eventName} event message.`);
            }
            else {
                messageChildLogger.info(`📦 Processing ${eventName} event message.`);
            }
            const successResult = await this.processSQSEventDataMessage(messageData, queueName);
            this.dispatchesPerMinute[queueName] += 1;
            const elapsedTimeSec = Date.now() / 1000 - startTimeSec;
            if (!successResult.handled) {
                this.prometheus.eventQueueProcessingFailed.inc({
                    eventName,
                    networkId,
                    reason: successResult.error ? 'ProcessingFailed:' + successResult.error : 'ProcessingFailed',
                });
                (0, logger_1.batchLogger)({ id: orderId, message: successResult.error }, `📦 Event ${eventName} has not been handled, will be retried.`);
                return;
            }
            this.handledDispatchesPerMinute[queueName] += 1;
            const result = await this.sqsService.deleteMessage(queueName, message.ReceiptHandle, messageChildLogger, Date.now() / 1000 - retrieveTimeSec);
            if (!result) {
                this.prometheus.eventQueueProcessingFailed.inc({ eventName, networkId, reason: 'DeleteMessageFailed' });
                return;
            }
            this.prometheus.eventQueueProcessing.inc({ eventName, networkId, sourceNetwork, destinationNetwork });
            this.prometheus.eventQueueProcessingTime.set({ eventName: messageData.eventName, networkId }, elapsedTimeSec);
            if (successResult.duplicated) {
                this.duplicatedDispatchesPerMinute[queueName] += 1;
                (0, logger_1.batchLogger)({ id: orderId, message: successResult.error, status: 'deleted:' + result }, `📦 Event ${eventName} has been handled, but is duplicated.`);
                return;
            }
            if (!successResult.success) {
                this.errorDispatchesPerMinute[queueName] += 1;
                this.prometheus.eventQueueProcessingFailed.inc({
                    eventName,
                    networkId,
                    reason: successResult.error ? 'ProcessingError:' + successResult.error : 'ProcessingError',
                });
                (0, logger_1.batchLogger)({ id: orderId, message: successResult.error }, `📦🚨 SQS Event ${eventName} errored.`);
                return;
            }
            messageChildLogger.info(`📦✅ Event ${eventName} has been processed`);
        }
        catch (error) {
            if (!error.message) {
                childLogger.error({ error: error.toString(), message: message.Body }, `🚨 Event processing: Error processing ${eventName} message`);
            }
            else {
                childLogger.error({ error: error.message, message: message.Body }, `Event processing: ${error.message}`);
            }
            this.unhlandledDispatchesPerMinute[queueName] += 1;
            this.prometheus.eventQueueProcessingFailed.inc({ eventName, networkId, reason: 'ProcessingFailed' });
            const elapsedTimeSec = Date.now() / 1000 - startTimeSec;
            this.prometheus.eventQueueProcessingTime.set({ eventName, networkId }, elapsedTimeSec);
        }
    }
}
exports.SQSDispatcher = SQSDispatcher;
//# sourceMappingURL=sqs.dispatcher.js.map