"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 __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 __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;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var WebhookService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebhookService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const bullmq_1 = require("@nestjs/bullmq");
const bullmq_2 = require("bullmq");
const crypto = __importStar(require("crypto"));
const entities_1 = require("../entities");
const dto_1 = require("../dto");
let WebhookService = WebhookService_1 = class WebhookService {
    constructor(webhookRepo, deliveryRepo, webhookQueue) {
        this.webhookRepo = webhookRepo;
        this.deliveryRepo = deliveryRepo;
        this.webhookQueue = webhookQueue;
        this.logger = new common_1.Logger(WebhookService_1.name);
    }
    generateSecret() {
        return `whsec_${crypto.randomBytes(32).toString('hex')}`;
    }
    signPayload(payload, secret) {
        const timestamp = Date.now();
        const body = JSON.stringify(payload);
        const signature = crypto
            .createHmac('sha256', secret)
            .update(`${timestamp}.${body}`)
            .digest('hex');
        return `t=${timestamp},v1=${signature}`;
    }
    async create(tenantId, userId, dto) {
        const invalidEvents = dto.events.filter((e) => !dto_1.WEBHOOK_EVENTS.includes(e));
        if (invalidEvents.length > 0) {
            throw new common_1.BadRequestException(`Invalid events: ${invalidEvents.join(', ')}`);
        }
        const webhook = this.webhookRepo.create({
            tenantId,
            name: dto.name,
            description: dto.description,
            url: dto.url,
            events: dto.events,
            headers: dto.headers || {},
            secret: this.generateSecret(),
            createdBy: userId,
        });
        const saved = await this.webhookRepo.save(webhook);
        this.logger.log(`Webhook created: ${saved.id} for tenant ${tenantId}`);
        return this.toResponse(saved, true);
    }
    async findAll(tenantId) {
        const webhooks = await this.webhookRepo.find({
            where: { tenantId },
            order: { createdAt: 'DESC' },
        });
        return Promise.all(webhooks.map((w) => this.toResponse(w)));
    }
    async findOne(tenantId, webhookId) {
        const webhook = await this.webhookRepo.findOne({
            where: { id: webhookId, tenantId },
        });
        if (!webhook) {
            throw new common_1.NotFoundException('Webhook not found');
        }
        return this.toResponse(webhook);
    }
    async update(tenantId, webhookId, dto) {
        const webhook = await this.webhookRepo.findOne({
            where: { id: webhookId, tenantId },
        });
        if (!webhook) {
            throw new common_1.NotFoundException('Webhook not found');
        }
        if (dto.events) {
            const invalidEvents = dto.events.filter((e) => !dto_1.WEBHOOK_EVENTS.includes(e));
            if (invalidEvents.length > 0) {
                throw new common_1.BadRequestException(`Invalid events: ${invalidEvents.join(', ')}`);
            }
        }
        Object.assign(webhook, {
            name: dto.name ?? webhook.name,
            description: dto.description ?? webhook.description,
            url: dto.url ?? webhook.url,
            events: dto.events ?? webhook.events,
            headers: dto.headers ?? webhook.headers,
            isActive: dto.isActive ?? webhook.isActive,
        });
        const saved = await this.webhookRepo.save(webhook);
        this.logger.log(`Webhook updated: ${saved.id}`);
        return this.toResponse(saved);
    }
    async remove(tenantId, webhookId) {
        const webhook = await this.webhookRepo.findOne({
            where: { id: webhookId, tenantId },
        });
        if (!webhook) {
            throw new common_1.NotFoundException('Webhook not found');
        }
        await this.webhookRepo.remove(webhook);
        this.logger.log(`Webhook deleted: ${webhookId}`);
    }
    async regenerateSecret(tenantId, webhookId) {
        const webhook = await this.webhookRepo.findOne({
            where: { id: webhookId, tenantId },
        });
        if (!webhook) {
            throw new common_1.NotFoundException('Webhook not found');
        }
        webhook.secret = this.generateSecret();
        await this.webhookRepo.save(webhook);
        return { secret: webhook.secret };
    }
    async testWebhook(tenantId, webhookId, dto) {
        const webhook = await this.webhookRepo.findOne({
            where: { id: webhookId, tenantId },
        });
        if (!webhook) {
            throw new common_1.NotFoundException('Webhook not found');
        }
        const eventType = dto.eventType || 'test.ping';
        const payload = dto.payload || {
            type: 'test.ping',
            timestamp: new Date().toISOString(),
            data: { message: 'This is a test webhook delivery' },
        };
        const delivery = this.deliveryRepo.create({
            webhookId: webhook.id,
            tenantId,
            eventType,
            payload,
            status: entities_1.DeliveryStatus.PENDING,
        });
        const saved = await this.deliveryRepo.save(delivery);
        await this.webhookQueue.add('deliver', {
            deliveryId: saved.id,
            webhookId: webhook.id,
            url: webhook.url,
            secret: webhook.secret,
            headers: webhook.headers,
            eventType,
            payload,
        }, { priority: 1 });
        this.logger.log(`Test webhook queued: ${saved.id}`);
        return this.toDeliveryResponse(saved);
    }
    async getDeliveries(tenantId, webhookId, query) {
        const webhook = await this.webhookRepo.findOne({
            where: { id: webhookId, tenantId },
        });
        if (!webhook) {
            throw new common_1.NotFoundException('Webhook not found');
        }
        const page = query.page || 1;
        const limit = Math.min(query.limit || 20, 100);
        const skip = (page - 1) * limit;
        const qb = this.deliveryRepo
            .createQueryBuilder('d')
            .where('d.webhook_id = :webhookId', { webhookId })
            .andWhere('d.tenant_id = :tenantId', { tenantId });
        if (query.status) {
            qb.andWhere('d.status = :status', { status: query.status });
        }
        if (query.eventType) {
            qb.andWhere('d.event_type = :eventType', { eventType: query.eventType });
        }
        qb.orderBy('d.created_at', 'DESC').skip(skip).take(limit);
        const [items, total] = await qb.getManyAndCount();
        return {
            items: items.map((d) => this.toDeliveryResponse(d)),
            total,
            page,
            limit,
            totalPages: Math.ceil(total / limit),
        };
    }
    async retryDelivery(tenantId, webhookId, deliveryId) {
        const delivery = await this.deliveryRepo.findOne({
            where: { id: deliveryId, webhookId, tenantId },
            relations: ['webhook'],
        });
        if (!delivery) {
            throw new common_1.NotFoundException('Delivery not found');
        }
        if (delivery.status !== entities_1.DeliveryStatus.FAILED) {
            throw new common_1.BadRequestException('Only failed deliveries can be retried');
        }
        delivery.status = entities_1.DeliveryStatus.RETRYING;
        delivery.attempt = 1;
        delivery.nextRetryAt = new Date();
        await this.deliveryRepo.save(delivery);
        await this.webhookQueue.add('deliver', {
            deliveryId: delivery.id,
            webhookId: delivery.webhookId,
            url: delivery.webhook.url,
            secret: delivery.webhook.secret,
            headers: delivery.webhook.headers,
            eventType: delivery.eventType,
            payload: delivery.payload,
        });
        this.logger.log(`Delivery retry queued: ${delivery.id}`);
        return this.toDeliveryResponse(delivery);
    }
    async getStats(webhookId) {
        const result = await this.deliveryRepo
            .createQueryBuilder('d')
            .select([
            'COUNT(*)::int as "totalDeliveries"',
            'COUNT(*) FILTER (WHERE d.status = :delivered)::int as "successfulDeliveries"',
            'COUNT(*) FILTER (WHERE d.status = :failed)::int as "failedDeliveries"',
            'COUNT(*) FILTER (WHERE d.status IN (:...pending))::int as "pendingDeliveries"',
            'MAX(d.delivered_at) as "lastDeliveryAt"',
        ])
            .where('d.webhook_id = :webhookId', { webhookId })
            .setParameters({
            delivered: entities_1.DeliveryStatus.DELIVERED,
            failed: entities_1.DeliveryStatus.FAILED,
            pending: [entities_1.DeliveryStatus.PENDING, entities_1.DeliveryStatus.RETRYING],
        })
            .getRawOne();
        const total = result.successfulDeliveries + result.failedDeliveries;
        const successRate = total > 0 ? Math.round((result.successfulDeliveries / total) * 100) : 0;
        return {
            ...result,
            successRate,
        };
    }
    async dispatch(tenantId, eventType, data) {
        const webhooks = await this.webhookRepo.find({
            where: { tenantId, isActive: true },
        });
        const subscribedWebhooks = webhooks.filter((w) => w.events.includes(eventType));
        if (subscribedWebhooks.length === 0) {
            return;
        }
        const payload = {
            type: eventType,
            timestamp: new Date().toISOString(),
            data,
        };
        for (const webhook of subscribedWebhooks) {
            const delivery = this.deliveryRepo.create({
                webhookId: webhook.id,
                tenantId,
                eventType,
                payload,
                status: entities_1.DeliveryStatus.PENDING,
            });
            const saved = await this.deliveryRepo.save(delivery);
            await this.webhookQueue.add('deliver', {
                deliveryId: saved.id,
                webhookId: webhook.id,
                url: webhook.url,
                secret: webhook.secret,
                headers: webhook.headers,
                eventType,
                payload,
            });
        }
        this.logger.log(`Event ${eventType} dispatched to ${subscribedWebhooks.length} webhooks for tenant ${tenantId}`);
    }
    getAvailableEvents() {
        return [
            { name: 'user.created', description: 'A new user was created' },
            { name: 'user.updated', description: 'A user was updated' },
            { name: 'user.deleted', description: 'A user was deleted' },
            { name: 'subscription.created', description: 'A new subscription was created' },
            { name: 'subscription.updated', description: 'A subscription was updated' },
            { name: 'subscription.cancelled', description: 'A subscription was cancelled' },
            { name: 'invoice.paid', description: 'An invoice was paid' },
            { name: 'invoice.failed', description: 'An invoice payment failed' },
            { name: 'file.uploaded', description: 'A file was uploaded' },
            { name: 'file.deleted', description: 'A file was deleted' },
            { name: 'tenant.updated', description: 'Tenant settings were updated' },
        ];
    }
    async toResponse(webhook, includeSecret = false) {
        const stats = await this.getStats(webhook.id);
        return {
            id: webhook.id,
            name: webhook.name,
            description: webhook.description,
            url: webhook.url,
            events: webhook.events,
            headers: webhook.headers,
            isActive: webhook.isActive,
            createdAt: webhook.createdAt,
            updatedAt: webhook.updatedAt,
            ...(includeSecret && { secret: webhook.secret }),
            stats,
        };
    }
    toDeliveryResponse(delivery) {
        return {
            id: delivery.id,
            webhookId: delivery.webhookId,
            eventType: delivery.eventType,
            payload: delivery.payload,
            status: delivery.status,
            responseStatus: delivery.responseStatus,
            responseBody: delivery.responseBody,
            attempt: delivery.attempt,
            maxAttempts: delivery.maxAttempts,
            nextRetryAt: delivery.nextRetryAt,
            lastError: delivery.lastError,
            createdAt: delivery.createdAt,
            deliveredAt: delivery.deliveredAt,
        };
    }
};
exports.WebhookService = WebhookService;
exports.WebhookService = WebhookService = WebhookService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(entities_1.WebhookEntity)),
    __param(1, (0, typeorm_1.InjectRepository)(entities_1.WebhookDeliveryEntity)),
    __param(2, (0, bullmq_1.InjectQueue)('webhooks')),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        bullmq_2.Queue])
], WebhookService);
//# sourceMappingURL=webhook.service.js.map