"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 EventWebhookService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventWebhookService = void 0;
const api_pagination_1 = require("@hed-hog/api-pagination");
const api_prisma_1 = require("@hed-hog/api-prisma");
const axios_1 = require("@nestjs/axios");
const common_1 = require("@nestjs/common");
const rxjs_1 = require("rxjs");
const integration_developer_api_service_1 = require("../integration/services/integration-developer-api.service");
const webhook_utils_1 = require("./webhook-utils");
let EventWebhookService = EventWebhookService_1 = class EventWebhookService {
    constructor(prisma, pagination, http, integrationApi) {
        this.prisma = prisma;
        this.pagination = pagination;
        this.http = http;
        this.integrationApi = integrationApi;
        this.logger = new common_1.Logger(EventWebhookService_1.name);
        this.registeredEvents = new Set();
    }
    get webhooks() {
        return this.prisma.event_webhook;
    }
    get webhookEvents() {
        return this.prisma.event_webhook_event;
    }
    get catalog() {
        return this.prisma.integration_event_catalog;
    }
    get logs() {
        return this.prisma.event_webhook_delivery_log;
    }
    async onModuleInit() {
        await this.registerConfiguredEvents();
    }
    async list(paginationParams) {
        const fields = ['slug', 'name', 'description', 'url', 'method', 'status'];
        const OR = this.prisma.createInsensitiveSearch(fields, paginationParams);
        return this.pagination.paginate(this.webhooks, paginationParams, {
            where: OR.length > 0 ? { OR } : {},
            include: {
                event_webhook_event: {
                    include: {
                        integration_event_catalog: true,
                    },
                },
            },
            orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
        });
    }
    async get(id) {
        const record = await this.webhooks.findUnique({
            where: { id },
            include: {
                event_webhook_event: {
                    include: {
                        integration_event_catalog: true,
                    },
                },
            },
        });
        if (!record) {
            throw new common_1.NotFoundException(`Event webhook with id ${id} not found.`);
        }
        return record;
    }
    async create(data) {
        await this.ensureSlugAvailable(data.slug);
        const record = await this.webhooks.create({
            data: Object.assign(Object.assign({}, this.normalizeWebhook(data)), { event_webhook_event: {
                    create: this.normalizeEventIds(data.event_ids).map((eventId) => ({
                        integration_event_catalog_id: eventId,
                    })),
                } }),
            include: {
                event_webhook_event: {
                    include: { integration_event_catalog: true },
                },
            },
        });
        await this.registerEventsFromWebhook(record);
        return record;
    }
    async update(id, data) {
        const existing = await this.get(id);
        if (data.slug && data.slug !== existing.slug) {
            await this.ensureSlugAvailable(data.slug);
        }
        const record = await this.webhooks.update({
            where: { id },
            data: this.normalizeWebhook(data, true),
            include: {
                event_webhook_event: {
                    include: { integration_event_catalog: true },
                },
            },
        });
        if (data.event_ids !== undefined) {
            await this.webhookEvents.deleteMany({
                where: { event_webhook_id: id },
            });
            const eventIds = this.normalizeEventIds(data.event_ids);
            for (const eventId of eventIds) {
                await this.webhookEvents.create({
                    data: {
                        event_webhook_id: id,
                        integration_event_catalog_id: eventId,
                    },
                });
            }
        }
        const refreshed = await this.get(id);
        await this.registerEventsFromWebhook(refreshed);
        return refreshed;
    }
    async delete(data) {
        var _a;
        if (!((_a = data.ids) === null || _a === void 0 ? void 0 : _a.length)) {
            throw new common_1.BadRequestException('You must select at least one item to delete.');
        }
        return this.webhooks.deleteMany({
            where: { id: { in: data.ids } },
        });
    }
    async listLogs(webhookId, paginationParams) {
        await this.get(webhookId);
        return this.pagination.paginate(this.logs, paginationParams, {
            where: { event_webhook_id: webhookId },
            orderBy: [{ created_at: 'desc' }, { id: 'desc' }],
        });
    }
    async handleEvent(event) {
        const webhooks = await this.findActiveWebhooksForEvent(event.eventName);
        for (const webhook of webhooks) {
            await this.deliverWebhook(webhook, event);
        }
    }
    async registerConfiguredEvents() {
        const rows = await this.catalog.findMany({
            where: {
                status: 'active',
                event_webhook_event: {
                    some: {
                        event_webhook: {
                            status: 'active',
                        },
                    },
                },
            },
            select: { slug: true },
        });
        for (const row of rows) {
            this.registerEvent(row.slug);
        }
    }
    async findActiveWebhooksForEvent(eventName) {
        return this.webhooks.findMany({
            where: {
                status: 'active',
                event_webhook_event: {
                    some: {
                        integration_event_catalog: {
                            slug: eventName,
                            status: 'active',
                        },
                    },
                },
            },
            include: {
                event_webhook_event: {
                    include: {
                        integration_event_catalog: true,
                    },
                },
            },
            orderBy: [{ priority: 'desc' }, { id: 'asc' }],
        });
    }
    async deliverWebhook(webhook, event) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
        const startedAt = Date.now();
        let attemptCount = 0;
        let lastError = null;
        let httpStatus = null;
        let responseSummary = null;
        const context = Object.assign(Object.assign({}, event), { timestamp: (_c = (_b = (_a = event.timestamp) === null || _a === void 0 ? void 0 : _a.toISOString) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : event.timestamp, outboxEventId: (_d = event.outboxEventId) !== null && _d !== void 0 ? _d : null });
        const url = (0, webhook_utils_1.renderTemplate)(webhook.url, context);
        const method = webhook.method || 'POST';
        const headers = (0, webhook_utils_1.renderTemplate)(webhook.headers || {}, context);
        const query = (0, webhook_utils_1.renderTemplate)(webhook.query || {}, context);
        const payload = (0, webhook_utils_1.renderTemplate)((_f = (_e = webhook.payload) !== null && _e !== void 0 ? _e : event.payload) !== null && _f !== void 0 ? _f : {}, context);
        const maxAttempts = Math.max(1, Number(webhook.retry_count || 0) + 1);
        for (attemptCount = 1; attemptCount <= maxAttempts; attemptCount++) {
            try {
                const result = await (0, rxjs_1.firstValueFrom)(this.http.request({
                    url,
                    method,
                    headers,
                    params: query,
                    data: ['GET', 'DELETE'].includes(method) ? undefined : payload,
                    timeout: Number(webhook.timeout_ms || 10000),
                }));
                httpStatus = result.status;
                responseSummary = (0, webhook_utils_1.summarizePayload)(result.data);
                lastError = null;
                break;
            }
            catch (error) {
                lastError = error;
                httpStatus = (_h = (_g = error === null || error === void 0 ? void 0 : error.response) === null || _g === void 0 ? void 0 : _g.status) !== null && _h !== void 0 ? _h : null;
                responseSummary = (0, webhook_utils_1.summarizePayload)((_j = error === null || error === void 0 ? void 0 : error.response) === null || _j === void 0 ? void 0 : _j.data);
            }
        }
        const success = !lastError;
        await this.logs.create({
            data: {
                event_webhook_id: webhook.id,
                integration_event_catalog_id: (_o = (_m = (_l = (_k = webhook.event_webhook_event) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.integration_event_catalog) === null || _m === void 0 ? void 0 : _m.id) !== null && _o !== void 0 ? _o : null,
                outbox_event_id: Number(event.outboxEventId || 0) || null,
                event_name: event.eventName,
                method,
                url,
                status: success ? 'success' : 'failed',
                http_status: httpStatus,
                duration_ms: Date.now() - startedAt,
                attempt_count: attemptCount,
                error_message: lastError
                    ? lastError instanceof Error
                        ? lastError.message
                        : String(lastError)
                    : null,
                request_summary: (0, webhook_utils_1.summarizePayload)({
                    headers,
                    query,
                    payload,
                }),
                response_summary: responseSummary,
            },
        });
        if (!success) {
            throw lastError;
        }
    }
    async registerEventsFromWebhook(webhook) {
        var _a;
        const eventNames = ((_a = webhook.event_webhook_event) === null || _a === void 0 ? void 0 : _a.map((row) => { var _a; return (_a = row.integration_event_catalog) === null || _a === void 0 ? void 0 : _a.slug; }).filter(Boolean)) || [];
        for (const eventName of eventNames) {
            this.registerEvent(eventName);
        }
    }
    registerEvent(eventName) {
        if (this.registeredEvents.has(eventName)) {
            return;
        }
        this.integrationApi.subscribe({
            eventName,
            consumerName: `core.event-webhook.${eventName}`,
            priority: -100,
            handler: async (event) => {
                await this.handleEvent(event);
            },
        });
        this.registeredEvents.add(eventName);
        this.logger.log(`Registered event webhook dispatcher for ${eventName}`);
    }
    async ensureSlugAvailable(slug) {
        const existing = await this.webhooks.findUnique({
            where: { slug: String(slug || '').trim() },
            select: { id: true },
        });
        if (existing) {
            throw new common_1.BadRequestException(`Event webhook "${slug}" already exists.`);
        }
    }
    normalizeWebhook(data, partial = false) {
        var _a, _b, _c, _d;
        const normalized = {};
        const assign = (key, value) => {
            if (!partial || value !== undefined) {
                normalized[key] = value;
            }
        };
        assign('slug', String((_a = data.slug) !== null && _a !== void 0 ? _a : '').trim());
        assign('name', String((_b = data.name) !== null && _b !== void 0 ? _b : '').trim());
        assign('description', this.nullableString(data.description, partial));
        assign('status', data.status === 'inactive' ? 'inactive' : 'active');
        assign('priority', Number((_c = data.priority) !== null && _c !== void 0 ? _c : 0));
        assign('url', String((_d = data.url) !== null && _d !== void 0 ? _d : '').trim());
        assign('method', String(data.method || 'POST').toUpperCase());
        assign('headers', (0, webhook_utils_1.normalizeJsonObject)(data.headers));
        assign('query', (0, webhook_utils_1.normalizeJsonObject)(data.query));
        assign('payload', (0, webhook_utils_1.normalizeJsonObject)(data.payload));
        assign('timeout_ms', Number(data.timeout_ms || 10000));
        assign('retry_count', Number(data.retry_count || 0));
        return normalized;
    }
    normalizeEventIds(value) {
        if (!value) {
            return [];
        }
        const values = Array.isArray(value) ? value : String(value).split(',');
        return values
            .map((item) => Number(item))
            .filter((item) => Number.isFinite(item) && item > 0);
    }
    nullableString(value, partial = false) {
        if (value === undefined) {
            return partial ? undefined : null;
        }
        if (value === null) {
            return null;
        }
        const normalized = String(value).trim();
        return normalized || null;
    }
};
exports.EventWebhookService = EventWebhookService;
exports.EventWebhookService = EventWebhookService = EventWebhookService_1 = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [api_prisma_1.PrismaService,
        api_pagination_1.PaginationService,
        axios_1.HttpService,
        integration_developer_api_service_1.IntegrationDeveloperApiService])
], EventWebhookService);
//# sourceMappingURL=event-webhook.service.js.map