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);
};
import DatabaseService from "./DatabaseService";
import Model from "../../Models/DatabaseModels/IncidentSla";
import OneUptimeDate from "../../Types/Date";
import IncidentSlaStatus from "../../Types/Incident/IncidentSlaStatus";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import logger from "../Utils/Logger";
import { IsBillingEnabled } from "../EnvironmentConfig";
import IncidentSlaRuleService from "./IncidentSlaRuleService";
import IncidentService from "./IncidentService";
import QueryHelper from "../Types/Database/QueryHelper";
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
export class Service extends DatabaseService {
    constructor() {
        super(Model);
        if (IsBillingEnabled) {
            this.hardDeleteItemsOlderThanInDays("createdAt", 3 * 365); // 3 years
        }
    }
    async createSlaForIncident(data) {
        var _a, _b, _c, _d;
        logger.debug(`Creating SLA record for incident ${data.incidentId} in project ${data.projectId}`, { projectId: (_a = data.projectId) === null || _a === void 0 ? void 0 : _a.toString() });
        // Find matching rule
        const matchingRuleArgs = {
            incidentId: data.incidentId,
            projectId: data.projectId,
        };
        if (data.incident) {
            matchingRuleArgs.incident = data.incident;
        }
        const matchingRule = await IncidentSlaRuleService.findMatchingRule(matchingRuleArgs);
        if (!matchingRule || !matchingRule.id) {
            logger.debug(`No matching SLA rule found for incident ${data.incidentId}`, { projectId: (_b = data.projectId) === null || _b === void 0 ? void 0 : _b.toString() });
            return null;
        }
        // Calculate deadlines
        const slaStartedAt = data.declaredAt;
        let responseDeadline = undefined;
        let resolutionDeadline = undefined;
        if (matchingRule.responseTimeInMinutes) {
            responseDeadline = OneUptimeDate.addRemoveMinutes(slaStartedAt, matchingRule.responseTimeInMinutes);
        }
        if (matchingRule.resolutionTimeInMinutes) {
            resolutionDeadline = OneUptimeDate.addRemoveMinutes(slaStartedAt, matchingRule.resolutionTimeInMinutes);
        }
        // Create SLA record
        const sla = new Model();
        sla.projectId = data.projectId;
        sla.incidentId = data.incidentId;
        sla.incidentSlaRuleId = matchingRule.id;
        sla.slaStartedAt = slaStartedAt;
        sla.status = IncidentSlaStatus.OnTrack;
        if (responseDeadline) {
            sla.responseDeadline = responseDeadline;
        }
        if (resolutionDeadline) {
            sla.resolutionDeadline = resolutionDeadline;
        }
        try {
            const createdSla = await this.create({
                data: sla,
                props: {
                    isRoot: true,
                },
            });
            logger.info(`Created SLA record ${createdSla.id} for incident ${data.incidentId} with rule ${matchingRule.name || matchingRule.id}`, { projectId: (_c = data.projectId) === null || _c === void 0 ? void 0 : _c.toString() });
            return createdSla;
        }
        catch (error) {
            logger.error(`Error creating SLA record for incident ${data.incidentId}: ${error}`, { projectId: (_d = data.projectId) === null || _d === void 0 ? void 0 : _d.toString() });
            return null;
        }
    }
    async markResponded(data) {
        var _a, _b;
        logger.debug(`Marking incident ${data.incidentId} as responded at ${data.respondedAt}`, { incidentId: (_a = data.incidentId) === null || _a === void 0 ? void 0 : _a.toString() });
        // Find all active SLA records for this incident
        const slaRecords = await this.findBy({
            query: {
                incidentId: data.incidentId,
                resolvedAt: QueryHelper.isNull(),
            },
            select: {
                _id: true,
                respondedAt: true,
                status: true,
                responseDeadline: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
        for (const sla of slaRecords) {
            // Only update if not already responded
            if (!sla.respondedAt && sla.id) {
                await this.updateOneById({
                    id: sla.id,
                    data: {
                        respondedAt: data.respondedAt,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                logger.info(`Marked SLA ${sla.id} as responded at ${data.respondedAt}`, { incidentId: (_b = data.incidentId) === null || _b === void 0 ? void 0 : _b.toString() });
            }
        }
    }
    async markResolved(data) {
        var _a, _b;
        logger.debug(`Marking incident ${data.incidentId} as resolved at ${data.resolvedAt}`, { incidentId: (_a = data.incidentId) === null || _a === void 0 ? void 0 : _a.toString() });
        // Find all active SLA records for this incident
        const slaRecords = await this.findBy({
            query: {
                incidentId: data.incidentId,
                resolvedAt: QueryHelper.isNull(),
            },
            select: {
                _id: true,
                status: true,
                resolutionDeadline: true,
                responseDeadline: true,
                respondedAt: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
        for (const sla of slaRecords) {
            if (!sla.id) {
                continue;
            }
            // Determine final SLA status
            let finalStatus = IncidentSlaStatus.Met;
            // Check if response deadline was breached
            if (sla.responseDeadline && !sla.respondedAt) {
                // Never responded, check if response deadline passed
                if (OneUptimeDate.isAfter(data.resolvedAt, sla.responseDeadline)) {
                    finalStatus = IncidentSlaStatus.ResponseBreached;
                }
            }
            else if (sla.responseDeadline &&
                sla.respondedAt &&
                OneUptimeDate.isAfter(sla.respondedAt, sla.responseDeadline)) {
                // Responded after deadline
                finalStatus = IncidentSlaStatus.ResponseBreached;
            }
            // Check if resolution deadline was breached (takes precedence)
            if (sla.resolutionDeadline &&
                OneUptimeDate.isAfter(data.resolvedAt, sla.resolutionDeadline)) {
                finalStatus = IncidentSlaStatus.ResolutionBreached;
            }
            await this.updateOneById({
                id: sla.id,
                data: {
                    resolvedAt: data.resolvedAt,
                    status: finalStatus,
                },
                props: {
                    isRoot: true,
                },
            });
            logger.info(`Marked SLA ${sla.id} as resolved with status ${finalStatus}`, { incidentId: (_b = data.incidentId) === null || _b === void 0 ? void 0 : _b.toString() });
        }
    }
    async recalculateDeadlines(data) {
        var _a, _b, _c, _d, _e, _f;
        logger.debug(`Recalculating deadlines for incident ${data.incidentId}`, {
            incidentId: (_a = data.incidentId) === null || _a === void 0 ? void 0 : _a.toString(),
        });
        // Get the incident to find the new severity and project
        const incident = await IncidentService.findOneById({
            id: data.incidentId,
            select: {
                projectId: true,
                declaredAt: true,
                incidentSeverityId: true,
                title: true,
                description: true,
                monitors: {
                    _id: true,
                    labels: {
                        _id: true,
                    },
                },
                labels: {
                    _id: true,
                },
            },
            props: {
                isRoot: true,
            },
        });
        if (!incident || !incident.projectId) {
            logger.warn(`Incident ${data.incidentId} not found`, {
                projectId: (_b = incident === null || incident === void 0 ? void 0 : incident.projectId) === null || _b === void 0 ? void 0 : _b.toString(),
            });
            return;
        }
        // Find all active SLA records for this incident
        const slaRecords = await this.findBy({
            query: {
                incidentId: data.incidentId,
                resolvedAt: QueryHelper.isNull(),
            },
            select: {
                _id: true,
                slaStartedAt: true,
                incidentSlaRuleId: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
        // Find new matching rule (may be different due to severity change)
        const newMatchingRule = await IncidentSlaRuleService.findMatchingRule({
            incidentId: data.incidentId,
            projectId: incident.projectId,
            incident: incident,
        });
        for (const sla of slaRecords) {
            if (!sla.id || !sla.slaStartedAt) {
                continue;
            }
            // Check if the matching rule has changed
            if (newMatchingRule &&
                ((_c = newMatchingRule.id) === null || _c === void 0 ? void 0 : _c.toString()) !== ((_d = sla.incidentSlaRuleId) === null || _d === void 0 ? void 0 : _d.toString())) {
                // Rule has changed, recalculate with new rule
                let responseDeadline = undefined;
                let resolutionDeadline = undefined;
                if (newMatchingRule.responseTimeInMinutes) {
                    responseDeadline = OneUptimeDate.addRemoveMinutes(sla.slaStartedAt, newMatchingRule.responseTimeInMinutes);
                }
                if (newMatchingRule.resolutionTimeInMinutes) {
                    resolutionDeadline = OneUptimeDate.addRemoveMinutes(sla.slaStartedAt, newMatchingRule.resolutionTimeInMinutes);
                }
                await this.updateOneById({
                    id: sla.id,
                    data: {
                        incidentSlaRuleId: newMatchingRule.id,
                        responseDeadline: responseDeadline || null,
                        resolutionDeadline: resolutionDeadline || null,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                logger.info(`Recalculated SLA ${sla.id} with new rule ${newMatchingRule.name || newMatchingRule.id}`, { projectId: (_e = incident.projectId) === null || _e === void 0 ? void 0 : _e.toString() });
            }
            else if (!newMatchingRule) {
                // No matching rule anymore, but keep the existing SLA record
                logger.debug(`No matching SLA rule found after severity change for incident ${data.incidentId}, keeping existing SLA`, { projectId: (_f = incident.projectId) === null || _f === void 0 ? void 0 : _f.toString() });
            }
        }
    }
    async getIncidentsNeedingInternalNoteReminder() {
        /*
         * Find SLAs where:
         * - Not resolved
         * - Has internal note reminder interval configured
         * - Last reminder was sent more than interval ago OR never sent
         */
        const now = OneUptimeDate.getCurrentDate();
        const slaRecords = await this.findBy({
            query: {
                resolvedAt: QueryHelper.isNull(),
            },
            select: {
                _id: true,
                incidentId: true,
                projectId: true,
                incidentSlaRuleId: true,
                incidentSlaRule: {
                    internalNoteReminderIntervalInMinutes: true,
                    internalNoteReminderTemplate: true,
                },
                slaStartedAt: true,
                responseDeadline: true,
                resolutionDeadline: true,
                status: true,
                lastInternalNoteReminderSentAt: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
        // Filter to only those needing reminders
        const needingReminder = slaRecords.filter((sla) => {
            var _a;
            const interval = (_a = sla.incidentSlaRule) === null || _a === void 0 ? void 0 : _a.internalNoteReminderIntervalInMinutes;
            if (!interval) {
                return false;
            }
            if (!sla.lastInternalNoteReminderSentAt) {
                // Never sent, check if enough time has passed since SLA started
                const timeSinceStart = OneUptimeDate.getDifferenceInMinutes(now, sla.slaStartedAt);
                return timeSinceStart >= interval;
            }
            const timeSinceLastReminder = OneUptimeDate.getDifferenceInMinutes(now, sla.lastInternalNoteReminderSentAt);
            return timeSinceLastReminder >= interval;
        });
        return needingReminder;
    }
    async getIncidentsNeedingPublicNoteReminder() {
        const now = OneUptimeDate.getCurrentDate();
        const slaRecords = await this.findBy({
            query: {
                resolvedAt: QueryHelper.isNull(),
            },
            select: {
                _id: true,
                incidentId: true,
                projectId: true,
                incidentSlaRuleId: true,
                incidentSlaRule: {
                    publicNoteReminderIntervalInMinutes: true,
                    publicNoteReminderTemplate: true,
                },
                slaStartedAt: true,
                responseDeadline: true,
                resolutionDeadline: true,
                status: true,
                lastPublicNoteReminderSentAt: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
        // Filter to only those needing reminders
        const needingReminder = slaRecords.filter((sla) => {
            var _a;
            const interval = (_a = sla.incidentSlaRule) === null || _a === void 0 ? void 0 : _a.publicNoteReminderIntervalInMinutes;
            if (!interval) {
                return false;
            }
            if (!sla.lastPublicNoteReminderSentAt) {
                // Never sent, check if enough time has passed since SLA started
                const timeSinceStart = OneUptimeDate.getDifferenceInMinutes(now, sla.slaStartedAt);
                return timeSinceStart >= interval;
            }
            const timeSinceLastReminder = OneUptimeDate.getDifferenceInMinutes(now, sla.lastPublicNoteReminderSentAt);
            return timeSinceLastReminder >= interval;
        });
        return needingReminder;
    }
    async getSlasNeedingBreachCheck() {
        // Find SLAs where status is OnTrack or AtRisk and not resolved
        return await this.findBy({
            query: {
                resolvedAt: QueryHelper.isNull(),
                status: QueryHelper.any([
                    IncidentSlaStatus.OnTrack,
                    IncidentSlaStatus.AtRisk,
                ]),
            },
            select: {
                _id: true,
                incidentId: true,
                projectId: true,
                incidentSlaRuleId: true,
                incidentSlaRule: {
                    atRiskThresholdInPercentage: true,
                    name: true,
                },
                slaStartedAt: true,
                responseDeadline: true,
                resolutionDeadline: true,
                respondedAt: true,
                status: true,
                breachNotificationSentAt: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
    }
    async getActiveSlasForIncident(data) {
        return await this.findBy({
            query: {
                incidentId: data.incidentId,
                resolvedAt: QueryHelper.isNull(),
            },
            select: {
                _id: true,
                incidentId: true,
                projectId: true,
                incidentSlaRuleId: true,
                incidentSlaRule: {
                    name: true,
                    responseTimeInMinutes: true,
                    resolutionTimeInMinutes: true,
                    atRiskThresholdInPercentage: true,
                },
                slaStartedAt: true,
                responseDeadline: true,
                resolutionDeadline: true,
                respondedAt: true,
                resolvedAt: true,
                status: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "createSlaForIncident", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "markResponded", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "markResolved", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "recalculateDeadlines", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], Service.prototype, "getIncidentsNeedingInternalNoteReminder", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], Service.prototype, "getIncidentsNeedingPublicNoteReminder", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], Service.prototype, "getSlasNeedingBreachCheck", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getActiveSlasForIncident", null);
export default new Service();
//# sourceMappingURL=IncidentSlaService.js.map