"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var OutboxProcessor_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutboxProcessor = void 0;
const common_1 = require("@nestjs/common");
const types_1 = require("../types");
const event_subscriber_registry_1 = require("./event-subscriber.registry");
const inbox_service_1 = require("./inbox.service");
const integration_link_service_1 = require("./integration-link.service");
const integration_settings_service_1 = require("./integration-settings.service");
const outbox_service_1 = require("./outbox.service");
let OutboxProcessor = OutboxProcessor_1 = class OutboxProcessor {
    constructor(outboxService, inboxService, linkService, registry, integrationSettingsService) {
        this.outboxService = outboxService;
        this.inboxService = inboxService;
        this.linkService = linkService;
        this.registry = registry;
        this.integrationSettingsService = integrationSettingsService;
        this.logger = new common_1.Logger(OutboxProcessor_1.name);
    }
    /**
     * Process a batch of pending events
     * Called in a loop or by scheduled job
     */
    async processBatch(options) {
        var _a;
        const settings = await this.integrationSettingsService.getRuntimeSettings();
        const batchSize = (_a = options === null || options === void 0 ? void 0 : options.batchSizeOverride) !== null && _a !== void 0 ? _a : settings.batchSize;
        const leaseMs = settings.processingLeaseMs;
        // Find pending events
        const events = await this.outboxService.findPending(batchSize, leaseMs);
        if (events.length === 0) {
            return 0;
        }
        this.logger.debug(`Processing ${events.length} outbox events`);
        let processedCount = 0;
        for (const event of events) {
            try {
                // Mark as processing with lease
                await this.outboxService.markProcessing(event.id, leaseMs);
                // Get handlers for this event
                const handlers = this.registry.getHandlers(event.eventName);
                if (handlers.length === 0) {
                    this.logger.warn(`No handlers registered for event: ${event.eventName}`);
                    // Mark as processed even though no handlers
                    await this.outboxService.updateStatus(event.id, types_1.OutboxEventStatus.PROCESSED, {
                        processedAt: new Date(),
                    });
                    processedCount++;
                    continue;
                }
                // Execute each handler
                let anyFailed = false;
                for (const handlerDef of handlers) {
                    const inboxEvent = await this.inboxService.getOrCreate(event.id, handlerDef.consumerName);
                    // Skip if already processed
                    if (inboxEvent.status === types_1.InboxEventStatus.PROCESSED) {
                        this.logger.debug(`Event ${event.id} already processed by ${handlerDef.consumerName}`);
                        continue;
                    }
                    await this.inboxService.markProcessing(inboxEvent.id);
                    try {
                        // Execute handler
                        await handlerDef.handler({
                            outboxEventId: event.id,
                            eventName: event.eventName,
                            sourceModule: event.sourceModule,
                            aggregateType: event.aggregateType,
                            aggregateId: event.aggregateId,
                            payload: event.payload,
                            timestamp: event.createdAt,
                        }, {
                            logger: this.logger,
                            inboxService: this.inboxService,
                            linkService: this.linkService,
                        });
                        // Mark as processed
                        await this.inboxService.markProcessed(inboxEvent.id);
                        this.logger.debug(`Handler ${handlerDef.consumerName} processed event ${event.id}`);
                    }
                    catch (error) {
                        anyFailed = true;
                        this.logger.error(`Handler ${handlerDef.consumerName} failed for event ${event.id}:`, error);
                        await this.inboxService.markFailed(inboxEvent.id, error instanceof Error ? error.message : String(error));
                    }
                }
                // Update outbox event status based on handler results
                if (!anyFailed) {
                    await this.outboxService.updateStatus(event.id, types_1.OutboxEventStatus.PROCESSED, {
                        processedAt: new Date(),
                    });
                    processedCount++;
                }
                else {
                    // At least one handler failed, retry logic applies
                    await this.handleEventFailure(event.id);
                }
            }
            catch (error) {
                this.logger.error(`Error processing event ${event.id}:`, error);
                await this.handleEventFailure(event.id);
            }
        }
        return processedCount;
    }
    /**
     * Handle event that failed to process
     */
    async handleEventFailure(eventId) {
        const settings = await this.integrationSettingsService.getRuntimeSettings();
        const maxAttempts = settings.maxAttempts;
        const retryBaseDelayMs = settings.retryBaseDelayMs;
        const deadLetterEnabled = settings.deadLetterEnabled;
        const event = await this.outboxService.getById(eventId);
        if (!event) {
            return;
        }
        const nextAttemptCount = event.attemptCount + 1;
        if (nextAttemptCount >= maxAttempts) {
            if (deadLetterEnabled) {
                this.logger.warn(`Event ${eventId} moved to dead letter after ${maxAttempts} attempts`);
                await this.outboxService.updateStatus(eventId, types_1.OutboxEventStatus.DEAD_LETTER, {
                    attemptCount: nextAttemptCount,
                });
            }
            else {
                this.logger.warn(`Event ${eventId} failed after ${maxAttempts} attempts (dead letter disabled)`);
                await this.outboxService.updateStatus(eventId, types_1.OutboxEventStatus.FAILED, {
                    attemptCount: nextAttemptCount,
                });
            }
        }
        else {
            // Calculate exponential backoff
            const delayMs = retryBaseDelayMs * Math.pow(2, event.attemptCount);
            const availableAt = new Date(Date.now() + delayMs);
            this.logger.debug(`Retrying event ${eventId} in ${delayMs}ms (attempt ${nextAttemptCount}/${maxAttempts})`);
            await this.outboxService.updateStatus(eventId, types_1.OutboxEventStatus.PENDING, {
                attemptCount: nextAttemptCount,
                availableAt,
            });
        }
    }
    /**
     * Process all pending events at startup (drain)
     */
    async startupDrain() {
        const settings = await this.integrationSettingsService.getRuntimeSettings();
        const drainBatchSize = settings.startupDrainBatchSize;
        this.logger.log(`Starting outbox drain with batch size: ${drainBatchSize}`);
        let totalProcessed = 0;
        // Keep processing until no more events
        let processed = 1;
        while (processed > 0) {
            processed = await this.processBatch({
                batchSizeOverride: drainBatchSize,
            });
            totalProcessed += processed;
            // Small delay to prevent overwhelming the system
            if (processed > 0) {
                await new Promise((resolve) => setTimeout(resolve, 100));
            }
        }
        this.logger.log(`Startup drain completed. Processed ${totalProcessed} events`);
        return totalProcessed;
    }
    /**
     * Get processor statistics
     */
    async getStats() {
        const pendingCount = await this.outboxService.countByStatus(types_1.OutboxEventStatus.PENDING);
        const processingCount = await this.outboxService.countByStatus(types_1.OutboxEventStatus.PROCESSING);
        const processedCount = await this.outboxService.countByStatus(types_1.OutboxEventStatus.PROCESSED);
        const failedCount = await this.outboxService.countByStatus(types_1.OutboxEventStatus.FAILED);
        const deadLetterCount = await this.outboxService.countByStatus(types_1.OutboxEventStatus.DEAD_LETTER);
        return {
            pending: pendingCount,
            processing: processingCount,
            processed: processedCount,
            failed: failedCount,
            dead_letter: deadLetterCount,
            total: pendingCount +
                processingCount +
                processedCount +
                failedCount +
                deadLetterCount,
            handlerCount: this.registry.getHandlerCount(),
        };
    }
};
exports.OutboxProcessor = OutboxProcessor;
exports.OutboxProcessor = OutboxProcessor = OutboxProcessor_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [outbox_service_1.OutboxService,
        inbox_service_1.InboxService,
        integration_link_service_1.IntegrationLinkService,
        event_subscriber_registry_1.EventSubscriberRegistry,
        integration_settings_service_1.IntegrationSettingsService])
], OutboxProcessor);
//# sourceMappingURL=outbox.processor.js.map