"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisSubscriber = void 0;
const common_1 = require("@nestjs/common");
const ioredis_1 = __importDefault(require("ioredis"));
const common_2 = require("./common");
class RedisSubscriber {
    constructor(mqMessageService, mqMessageQueueService) {
        this.mqMessageService = mqMessageService;
        this.mqMessageQueueService = mqMessageQueueService;
        this.logger = new common_1.Logger(RedisSubscriber.name);
        this.client = null;
        this.reconnectAttempt = 0;
        this.stopping = false;
        this.serviceRole = process.env.QUEUES_SERVICE_ROLE || 'both';
        if (!process.env.QUEUES_SERVICE_ROLE) {
            this.logger.debug('QUEUES_SERVICE_ROLE is not defined. Defaulting RedisSubscriber service role to "both".');
        }
        if (!process.env.QUEUES_REDIS_URL) {
            this.logger.debug('RedisSubscriber: QUEUES_REDIS_URL is not defined in the environment variables');
        }
    }
    async onModuleInit() {
        const defaultBroker = process.env.QUEUES_DEFAULT_BROKER || 'database';
        const solidCliRunning = process.env.SOLID_CLI_RUNNING || 'false';
        const queueNameRegex = (process.env.QUEUES_QUEUE_NAME_REGEX_TO_ENABLE || '').trim();
        if (process.env.QUEUES_REDIS_URL &&
            ['both', 'subscriber'].includes(this.serviceRole) &&
            solidCliRunning === 'false' &&
            defaultBroker === 'redis') {
            const options = this.options();
            const queueName = options.queueName;
            if (queueNameRegex && queueNameRegex !== 'all') {
                try {
                    const regex = new RegExp(queueNameRegex);
                    if (!regex.test(queueName)) {
                        this.logger.log(`RedisSubscriber for queue ${queueName} is disabled because it does not match QUEUES_QUEUE_NAME_REGEX_TO_ENABLE=${queueNameRegex}`);
                        return;
                    }
                }
                catch (error) {
                    this.logger.error(`Invalid QUEUES_QUEUE_NAME_REGEX_TO_ENABLE regex "${queueNameRegex}". Subscriber for queue ${queueName} will not start.`);
                    return;
                }
            }
            const namespacedQueueName = (0, common_2.buildNamespacedQueueName)(queueName);
            await this.connectAndSubscribe(namespacedQueueName);
            this.logger.log(`RedisSubscriber ready to consume messages: ${JSON.stringify(options)}`);
        }
    }
    async onModuleDestroy() {
        this.stopping = true;
        await this.cleanup();
    }
    async connectAndSubscribe(channel) {
        await this.cleanup();
        const client = new ioredis_1.default(process.env.QUEUES_REDIS_URL);
        this.client = client;
        client.on('error', (err) => {
            if (client !== this.client)
                return;
            this.logger.error(`RedisSubscriber connection error for channel ${channel}: ${err.message}`, err.stack);
        });
        client.on('close', () => {
            if (client !== this.client)
                return;
            this.logger.warn(`RedisSubscriber connection closed for channel ${channel}`);
            this.triggerReconnect(channel, 'connection closed');
        });
        client.on('message', async (receivedChannel, rawMessage) => {
            if (receivedChannel !== channel)
                return;
            let message = null;
            try {
                message = JSON.parse(rawMessage);
            }
            catch (error) {
                this.logger.error(`RedisSubscriber invalid JSON on channel ${channel}: ${error.message}`);
                return;
            }
            if (!message.retryCount)
                message.retryCount = 0;
            if (!message.retryInterval)
                message.retryInterval = 1000;
            if (!message.currentRetry)
                message.currentRetry = 0;
            try {
                await this.processMessage(message);
            }
            catch (error) {
                await this.handleProcessingError(message, error, channel);
            }
        });
        await client.subscribe(channel);
        this.reconnectAttempt = 0;
    }
    async handleProcessingError(message, error, channel) {
        const errorMessage = error?.message || String(error);
        this.logger.error(`Error processing message on channel ${channel}: ${errorMessage}`);
        if (message.currentRetry < message.retryCount) {
            await this.updateStatusInDatabase('retrying', message);
            message.currentRetry++;
            this.logger.warn(`Retrying message (${message.currentRetry}/${message.retryCount}) after ${message.retryInterval}ms on channel ${channel}`);
            setTimeout(async () => {
                try {
                    await this.processMessage(message);
                }
                catch (retryError) {
                    await this.handleProcessingError(message, retryError, channel);
                }
            }, message.retryInterval);
        }
        else {
            await this.updateStatusInDatabase('failed', message, errorMessage, '');
            this.logger.error(`Message failed after ${message.retryCount} attempts on channel ${channel}: ${errorMessage}`);
        }
    }
    triggerReconnect(channel, reason) {
        if (this.stopping)
            return;
        this.reconnectAttempt++;
        const delay = this.backoff();
        this.logger.warn(`RedisSubscriber reconnecting for channel ${channel} (${reason}) in ${delay}ms`);
        setTimeout(async () => {
            if (this.stopping)
                return;
            try {
                await this.connectAndSubscribe(channel);
                this.logger.log(`RedisSubscriber reconnected for channel ${channel}`);
            }
            catch (err) {
                this.triggerReconnect(channel, `reconnect failed: ${err.message}`);
            }
        }, delay);
    }
    async cleanup() {
        const client = this.client;
        this.client = null;
        if (client) {
            try {
                await client.quit();
            }
            catch (_) {
            }
        }
    }
    backoff() {
        const baseMs = 1000;
        const maxMs = 30_000;
        const exp = Math.min(maxMs, baseMs * Math.pow(2, this.reconnectAttempt));
        const jitter = Math.floor(Math.random() * (exp * 0.2));
        return Math.min(maxMs, exp + jitter);
    }
    async processMessage(message) {
        await this.updateStatusInDatabase('started', message);
        const result = await this.subscribe(message);
        await this.updateStatusInDatabase('succeeded', message, '', result != null ? JSON.stringify(result, null, 2) : '');
    }
    async updateStatusInDatabase(stage, message, error = '', result = '') {
        try {
            const mqMessage = await this.mqMessageService.repo.findOne({
                where: { messageId: message.messageId },
            });
            if (mqMessage) {
                const updatedFields = { stage };
                if (stage === 'failed' || stage === 'succeeded') {
                    updatedFields['finishedAt'] = new Date();
                    updatedFields['elapsedMillis'] = updatedFields['finishedAt'].getTime() - mqMessage.startedAt.getTime();
                }
                if (stage === 'succeeded')
                    updatedFields['output'] = result;
                if (stage === 'failed')
                    updatedFields['error'] = error;
                await this.mqMessageService.repo.update(mqMessage.id, updatedFields);
            }
        }
        catch (err) {
            this.logger.error(err.message, err.stack);
        }
    }
}
exports.RedisSubscriber = RedisSubscriber;
//# sourceMappingURL=redis-subscriber.service.js.map