"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.RabbitMqPublisher = void 0;
const common_1 = require("@nestjs/common");
const amqp = __importStar(require("amqplib"));
const uuid_1 = require("uuid");
const common_2 = require("./common");
class RabbitMqPublisher {
    constructor(mqMessageService, mqMessageQueueService) {
        this.mqMessageService = mqMessageService;
        this.mqMessageQueueService = mqMessageQueueService;
        this.logger = new common_1.Logger(RabbitMqPublisher.name);
        this.connection = null;
        this.channel = null;
        this.connectingPromise = null;
        this.url = process.env.QUEUES_RABBIT_MQ_URL;
        this.serviceRole = process.env.QUEUES_SERVICE_ROLE || 'both';
        if (!this.url) {
            this.logger.debug('RabbitMqPublisher url is not defined in the environment variables');
        }
        if (!process.env.QUEUES_SERVICE_ROLE) {
            this.logger.debug('QUEUES_SERVICE_ROLE is not defined. Defaulting RabbitMqPublisher service role to "both".');
        }
    }
    shouldPersistToDatabase() {
        return this.options().persistToDatabase ?? true;
    }
    async ensureConnectionAndChannel() {
        if (this.channel) {
            return this.channel;
        }
        if (this.connectingPromise) {
            await this.connectingPromise;
            if (this.channel)
                return this.channel;
        }
        this.connectingPromise = (async () => {
            const url = new URL(this.url);
            const conn = await amqp.connect({
                protocol: url.protocol.replace(':', ''),
                hostname: url.hostname,
                port: parseInt(url.port),
                username: url.username,
                password: decodeURIComponent(url.password),
                frameMax: 131072,
            });
            conn.on('error', (err) => {
                this.logger.error(`RabbitMQ connection error: ${err.message}`, err.stack);
            });
            conn.on('close', () => {
                this.logger.warn('RabbitMQ connection closed, resetting');
                this.connection = null;
                this.channel = null;
            });
            const channel = await conn.createChannel();
            channel.on('return', (msg) => {
                const content = msg.content?.toString?.() ?? '';
                this.logger.warn(`RabbitMqPublisher message returned from exchange ${msg.fields.exchange} with routingKey ${msg.fields.routingKey}: ${content}`);
            });
            const options = this.options();
            const queueName = options.queueName;
            const namespacedQueueName = (0, common_2.buildNamespacedQueueName)(queueName);
            const exchangeName = `${namespacedQueueName}.exchange`;
            const routingKey = `${namespacedQueueName}.routing-key`;
            await channel.assertExchange(exchangeName, 'direct', {});
            const queue = await channel.assertQueue(namespacedQueueName, {});
            await channel.bindQueue(queue.queue, exchangeName, routingKey);
            this.connection = conn;
            this.channel = channel;
        })();
        try {
            await this.connectingPromise;
        }
        finally {
            this.connectingPromise = null;
        }
        if (!this.channel) {
            throw new Error('Failed to initialize RabbitMQ channel');
        }
        return this.channel;
    }
    async onModuleDestroy() {
        await this.closeConnectionAndChannel();
    }
    async closeConnectionAndChannel() {
        if (this.channel) {
            try {
                await this.channel.close();
            }
            catch (err) {
                this.logger.warn(`RabbitMqPublisher error closing channel: ${err.message}`);
            }
            finally {
                this.channel = null;
            }
        }
        if (this.connection) {
            try {
                await this.connection.close();
            }
            catch (err) {
                this.logger.warn(`RabbitMqPublisher error closing connection: ${err.message}`);
            }
            finally {
                this.connection = null;
            }
        }
    }
    async publish(message) {
        if (!this.url) {
            this.logger.error('RabbitMqPublisher url is not defined in the environment variables');
            throw new Error('RabbitMqPublisher url is not defined in the environment variables');
        }
        if (!this.serviceRole) {
            this.logger.error('Queue service Role is not defined in the environment variables');
            throw new Error('Queue service Role is not defined in the environment variables');
        }
        if (this.serviceRole === 'subscriber') {
            this.logger.error('Queue service Role is subscriber, cannot publish messages');
            throw new Error('Queue service Role is subscriber, cannot publish messages');
        }
        const channel = await this.ensureConnectionAndChannel();
        const options = this.options();
        const queueName = options.queueName;
        const namespacedQueueName = (0, common_2.buildNamespacedQueueName)(queueName);
        const exchangeName = `${namespacedQueueName}.exchange`;
        const routingKey = `${namespacedQueueName}.routing-key`;
        if (!message.retryCount)
            message.retryCount = 0;
        if (!message.retryInterval)
            message.retryInterval = 1000;
        message.messageId = (0, uuid_1.v4)();
        if (this.shouldPersistToDatabase()) {
            await this.persistToDatabase(namespacedQueueName, message);
        }
        try {
            const publishStatus = channel.publish(exchangeName, routingKey, Buffer.from(JSON.stringify(message)), { mandatory: true });
        }
        catch (err) {
            this.logger.error(`RabbitMqPublisher Message publish failed: ${JSON.stringify(err)}`);
            if (err instanceof Error) {
                this.logger.error(`RabbitMqPublisher Error stack: ${err.stack}`);
            }
        }
        finally {
        }
        return message.messageId;
    }
    async persistToDatabase(queueName, message) {
        try {
            const mqMessageQueue = await this.mqMessageQueueService.resolveQueue(queueName);
            await this.mqMessageService.create({
                messageBroker: this.options().type,
                messageId: message.messageId,
                retryCount: message.retryCount,
                retryInterval: message.retryInterval,
                stage: 'pending',
                startedAt: new Date(),
                input: JSON.stringify(message, null, 2),
                parentEntityId: message.parentEntityId,
                parentEntity: message.parentEntity,
                mqMessageQueueId: mqMessageQueue.id,
            });
        }
        catch (error) {
            this.logger.error(error.message, error.stack);
        }
    }
}
exports.RabbitMqPublisher = RabbitMqPublisher;
//# sourceMappingURL=rabbitmq-publisher.service.js.map