"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SQSPublisher = void 0;
const tslib_1 = require("tslib");
const logger_1 = require("../utils/logger");
const helpers_1 = require("../utils/helpers");
const node_crypto_1 = tslib_1.__importDefault(require("node:crypto"));
const redis_1 = require("redis");
const sieve_redis_1 = require("../transmitter/sieve.redis");
class SQSPublisher {
    sqsService;
    config;
    prometheus;
    startedQueues;
    dispatchesPerMinute;
    unhlandledDispatchesPerMinute;
    errorDispatchesPerMinute;
    duplicatedDispatchesPerMinute;
    handledDispatchesPerMinute;
    sieve = null;
    redisClient;
    constructor(sqsService, config, prometheus) {
        this.sqsService = sqsService;
        this.config = config;
        this.prometheus = prometheus;
        this.startedQueues = [];
        this.unhlandledDispatchesPerMinute = {};
        this.dispatchesPerMinute = {};
        this.errorDispatchesPerMinute = {};
        this.duplicatedDispatchesPerMinute = {};
        this.handledDispatchesPerMinute = {};
    }
    async reportStatsPerQueue(queueName) {
        logger_1.logger.info({ queueName }, '🚀 Starting SQS publisher stats reporting...');
        while (true) {
            await (0, helpers_1.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 = 'publish';
            // Report stats to Prometheus
            this.prometheus.eventQueueProcessingHealthcheckRates.inc({
                queueName,
                sqsDirectionLabel,
                label: 'handled',
            }, handled);
            this.prometheus.eventQueueProcessingHealthcheckRates.inc({
                queueName,
                sqsDirectionLabel,
                label: 'error',
            }, error);
            this.prometheus.eventQueueProcessingHealthcheckRates.inc({
                queueName,
                sqsDirectionLabel,
                label: 'dispatches',
            }, dispatches);
            this.prometheus.eventQueueProcessingHealthcheckRates.inc({
                queueName,
                sqsDirectionLabel,
                label: 'duplicated',
            }, duplicated);
            this.prometheus.eventQueueProcessingHealthcheckRates.inc({
                queueName,
                sqsDirectionLabel,
                label: 'unhandled',
            }, unhandled);
            logger_1.logger.info({
                queueName,
                sqsDirectionLabel,
                dispatches,
                handled,
                error,
                duplicated,
                unhandled,
            }, '📊 SQS dispatcher stats');
            // Reset counters
            this.resetQueueRateCounters(queueName);
        }
    }
    async init() {
        logger_1.logger.info({ url: this.config.redis.cacheUrl }, '🚀 Starting Redis Client for SQS Publisher...');
        try {
            this.redisClient = (0, redis_1.createClient)({ url: this.config.redis.cacheUrl });
            await this.redisClient.connect();
            this.redisClient.on('error', (err) => {
                logger_1.logger.error({ error: JSON.stringify(err) }, 'Redis Client Error');
            });
        }
        catch (error) {
            logger_1.logger.error({ error: JSON.stringify(error) }, 'Error creating Redis client');
            process.exit(1);
        }
        // Set deduplication key with TTL aligned with pod-1 of messiah pickup of the dropped events
        this.sieve = sieve_redis_1.RedisSieve.getInstance(this.redisClient, Math.floor((this.config.messiah.pod1MinOffsetMinutes * 60) / 2) * 1000);
        void this.sieve.start();
    }
    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;
    }
    generateMessageId(args) {
        return node_crypto_1.default.createHash('md5').update(JSON.stringify(args)).digest('hex');
    }
    async safeAddItemToSieve(messageId, eventName, orderId) {
        if (!this.config.aws.sqs.preCheckForDedup) {
            return false;
        }
        if (!this.sieve) {
            return false;
        }
        try {
            const isFirstTimeAdded = await this.sieve.addItem(messageId, eventName, orderId);
            if (isFirstTimeAdded) {
                logger_1.logger.debug({ messageId, eventName, orderId }, '✅ Successfully added item to Redis Sieve for deduplication');
                return false; // Not a duplicate
            }
            logger_1.logger.debug({ messageId, eventName, orderId }, '🟡 Item already exists in Redis Sieve for deduplication');
            return true; // Duplicate found
        }
        catch (error) {
            logger_1.logger.error({
                error: error.message,
                messageId,
                eventName,
                orderId,
            }, '🚨 Error while checking Redis Sieve for deduplication');
            return false;
        }
    }
    async pushSQSEvent(networkId, contractName, eventName, eventData, args, orderId, sourceNetwork, destinationNetwork, childLogger, queueName = this.config.aws.sqs.eventListenerQueue) {
        const messageId = this.generateMessageId(args);
        if (!orderId) {
            (0, logger_1.batchLogger)({ message: eventName, source: networkId }, '❌ No order ID provided, while pushing to SQS', 30);
        }
        this.dispatchesPerMinute[queueName] = (this.dispatchesPerMinute[queueName] || 0) + 1;
        try {
            // Check Redis for deduplication
            const alreadyExists = await this.safeAddItemToSieve(messageId, eventName, orderId);
            if (alreadyExists) {
                this.duplicatedDispatchesPerMinute[queueName] = (this.duplicatedDispatchesPerMinute[queueName] || 0) + 1;
                (0, logger_1.batchLogger)({ message: eventName, txHash: messageId, id: orderId }, '🟡 Duplicate SQS message suppressed', 30);
                return {
                    error: 'duplicate',
                    handled: true,
                    duplicated: true,
                    success: false,
                };
            }
            const messageBody = JSON.stringify({
                eventName,
                networkId,
                contractName,
                eventData,
                args,
                orderId,
                sourceNetwork,
                destinationNetwork,
                originApp: this.config.appName,
            });
            const messageAttributes = {
                orderId: {
                    DataType: 'String',
                    StringValue: orderId,
                },
                messageId: {
                    DataType: 'String',
                    StringValue: messageId,
                },
            };
            const options = queueName.includes('.fifo')
                ? {
                    messageGroupId: messageId,
                    messageDeduplicationId: messageId,
                    messageAttributes,
                }
                : {};
            const pushResult = await this.sqsService.pushMessage(queueName, messageBody, childLogger, options);
            if (!pushResult) {
                return {
                    error: 'SQS Push Failed',
                    handled: false,
                    duplicated: false,
                    success: false,
                };
            }
            this.handledDispatchesPerMinute[queueName] = (this.handledDispatchesPerMinute[queueName] || 0) + 1;
            return {
                handled: true,
                duplicated: false,
                success: true,
            };
        }
        catch (error) {
            this.errorDispatchesPerMinute[queueName] = (this.errorDispatchesPerMinute[queueName] || 0) + 1;
            childLogger.error({
                error: error.message,
                eventData: JSON.stringify(eventData),
                messageId,
            }, '🚨 Could not push message to SQS');
            return {
                handled: false,
                duplicated: false,
                success: false,
            };
        }
    }
}
exports.SQSPublisher = SQSPublisher;
//# sourceMappingURL=sqs.publisher.js.map