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 Alert from "../../../Models/DatabaseModels/Alert";
import AlertStateTimeline from "../../../Models/DatabaseModels/AlertStateTimeline";
import Label from "../../../Models/DatabaseModels/Label";
import OnCallDutyPolicy from "../../../Models/DatabaseModels/OnCallDutyPolicy";
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
import BadDataException from "../../../Types/Exception/BadDataException";
import { DisableAutomaticAlertCreation } from "../../EnvironmentConfig";
import AlertService from "../../Services/AlertService";
import AlertSeverityService from "../../Services/AlertSeverityService";
import AlertStateTimelineService from "../../Services/AlertStateTimelineService";
import HostService from "../../Services/HostService";
import ServiceService from "../../Services/ServiceService";
import logger from "../Logger";
import CaptureSpan from "../Telemetry/CaptureSpan";
import MonitorTemplateUtil from "./MonitorTemplateUtil";
import OneUptimeDate from "../../../Types/Date";
export default class MonitorAlert {
    static async checkOpenAlertsAndCloseIfResolved(input) {
        var _a, _b, _c, _d, _e;
        // check active alerts and if there are open alerts, do not create another alert.
        const openAlerts = await AlertService.findBy({
            query: {
                monitor: input.monitorId,
                currentAlertState: {
                    isResolvedState: false,
                },
            },
            skip: 0,
            limit: LIMIT_PER_PROJECT,
            select: {
                _id: true,
                title: true,
                createdCriteriaId: true,
                projectId: true,
                alertNumber: true,
                alertNumberWithPrefix: true,
                currentAlertStateId: true,
                seriesFingerprint: true,
                seriesLabels: true,
            },
            props: {
                isRoot: true,
            },
        });
        // check if should close the alert.
        for (const openAlert of openAlerts) {
            const shouldClose = this.shouldCloseAlert({
                openAlert,
                autoResolveCriteriaInstanceIdAlertIdsDictionary: input.autoResolveCriteriaInstanceIdAlertIdsDictionary,
                criteriaInstance: input.criteriaInstance,
                breachingSeriesFingerprints: input.breachingSeriesFingerprints,
            });
            if (shouldClose) {
                // then resolve alert.
                await this.resolveOpenAlert({
                    openAlert: openAlert,
                    rootCause: input.rootCause,
                    dataToProcess: input.dataToProcess,
                });
                (_a = input.evaluationSummary) === null || _a === void 0 ? void 0 : _a.events.push({
                    type: "alert-resolved",
                    title: `Alert resolved: ${(_b = openAlert.id) === null || _b === void 0 ? void 0 : _b.toString()}`,
                    message: "Alert auto-resolved because autoresolve is enabled for this criteria.",
                    relatedAlertId: (_c = openAlert.id) === null || _c === void 0 ? void 0 : _c.toString(),
                    relatedAlertNumber: openAlert.alertNumber,
                    relatedAlertNumberWithPrefix: openAlert.alertNumberWithPrefix,
                    relatedCriteriaId: (_e = (_d = input.criteriaInstance) === null || _d === void 0 ? void 0 : _d.data) === null || _e === void 0 ? void 0 : _e.id,
                    at: OneUptimeDate.getCurrentDate(),
                });
            }
        }
        return openAlerts;
    }
    static async criteriaMetCreateAlertsAndUpdateMonitorStatus(input) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
        const alertLogAttributes = {
            projectId: (_a = input.monitor.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
        };
        // check open alerts
        logger.debug(`${(_b = input.monitor.id) === null || _b === void 0 ? void 0 : _b.toString()} - Check open alerts.`, alertLogAttributes);
        const breachingSeriesFingerprints = input.matchesPerSeries
            ? new Set(input.matchesPerSeries.map((m) => {
                return m.fingerprint;
            }))
            : undefined;
        // check active alerts and if there are open alerts, do not cretae anothr alert.
        const openAlerts = await this.checkOpenAlertsAndCloseIfResolved({
            monitorId: input.monitor.id,
            autoResolveCriteriaInstanceIdAlertIdsDictionary: input.autoResolveCriteriaInstanceIdAlertIdsDictionary,
            rootCause: input.rootCause,
            criteriaInstance: input.criteriaInstance,
            dataToProcess: input.dataToProcess,
            evaluationSummary: input.evaluationSummary,
            breachingSeriesFingerprints,
        });
        if (!((_c = input.criteriaInstance.data) === null || _c === void 0 ? void 0 : _c.createAlerts)) {
            return;
        }
        const seriesToProcess = input.matchesPerSeries && input.matchesPerSeries.length > 0
            ? input.matchesPerSeries
            : [undefined];
        for (const criteriaAlert of ((_d = input.criteriaInstance.data) === null || _d === void 0 ? void 0 : _d.alerts) || []) {
            for (const seriesMatch of seriesToProcess) {
                const seriesFingerprint = seriesMatch === null || seriesMatch === void 0 ? void 0 : seriesMatch.fingerprint;
                const seriesLabels = seriesMatch === null || seriesMatch === void 0 ? void 0 : seriesMatch.labels;
                const seriesRootCause = (seriesMatch === null || seriesMatch === void 0 ? void 0 : seriesMatch.rootCause) || input.rootCause;
                /*
                 * Per-series scheduled-maintenance suppression: skip creating an
                 * alert for a series whose resource is inside an ongoing
                 * maintenance window. Other series on the same monitor are
                 * unaffected. Only *new* creation is suppressed — existing open
                 * alerts follow the normal resolve path.
                 */
                if (seriesFingerprint &&
                    ((_e = input.suppressedSeriesFingerprints) === null || _e === void 0 ? void 0 : _e.has(seriesFingerprint))) {
                    logger.debug(`${(_f = input.monitor.id) === null || _f === void 0 ? void 0 : _f.toString()} - Skipping alert for series ${seriesFingerprint}: its resource is under an active scheduled maintenance window.`, alertLogAttributes);
                    (_g = input.evaluationSummary) === null || _g === void 0 ? void 0 : _g.events.push({
                        type: "alert-skipped",
                        title: "Alert suppressed by scheduled maintenance",
                        message: "Skipped creating an alert because the resource for this series is under an active scheduled maintenance window.",
                        relatedCriteriaId: (_h = input.criteriaInstance.data) === null || _h === void 0 ? void 0 : _h.id,
                        at: OneUptimeDate.getCurrentDate(),
                    });
                    continue;
                }
                const alreadyOpenAlert = openAlerts.find((alert) => {
                    var _a;
                    return (alert.createdCriteriaId ===
                        ((_a = input.criteriaInstance.data) === null || _a === void 0 ? void 0 : _a.id.toString()) &&
                        (alert.seriesFingerprint || undefined) === seriesFingerprint);
                });
                const hasAlreadyOpenAlert = Boolean(alreadyOpenAlert);
                logger.debug(`${(_j = input.monitor.id) === null || _j === void 0 ? void 0 : _j.toString()} - Open Alert ${(_k = alreadyOpenAlert === null || alreadyOpenAlert === void 0 ? void 0 : alreadyOpenAlert.id) === null || _k === void 0 ? void 0 : _k.toString()}`, alertLogAttributes);
                logger.debug(`${(_l = input.monitor.id) === null || _l === void 0 ? void 0 : _l.toString()} - Has open alert ${hasAlreadyOpenAlert}`, alertLogAttributes);
                if (hasAlreadyOpenAlert) {
                    const renderedAlertTitle = (alreadyOpenAlert === null || alreadyOpenAlert === void 0 ? void 0 : alreadyOpenAlert.title) || criteriaAlert.title;
                    (_m = input.evaluationSummary) === null || _m === void 0 ? void 0 : _m.events.push({
                        type: "alert-skipped",
                        title: `Alert already active: ${renderedAlertTitle}`,
                        message: "Skipped creating a new alert because an active alert exists for this criteria.",
                        relatedCriteriaId: (_o = input.criteriaInstance.data) === null || _o === void 0 ? void 0 : _o.id,
                        relatedAlertId: (_p = alreadyOpenAlert === null || alreadyOpenAlert === void 0 ? void 0 : alreadyOpenAlert.id) === null || _p === void 0 ? void 0 : _p.toString(),
                        relatedAlertNumber: alreadyOpenAlert === null || alreadyOpenAlert === void 0 ? void 0 : alreadyOpenAlert.alertNumber,
                        relatedAlertNumberWithPrefix: alreadyOpenAlert === null || alreadyOpenAlert === void 0 ? void 0 : alreadyOpenAlert.alertNumberWithPrefix,
                        at: OneUptimeDate.getCurrentDate(),
                    });
                    continue;
                }
                // create alert here.
                logger.debug(`${(_q = input.monitor.id) === null || _q === void 0 ? void 0 : _q.toString()} - Create alert.`, alertLogAttributes);
                const alert = new Alert();
                const storageMap = MonitorTemplateUtil.buildTemplateStorageMap({
                    monitorType: input.monitor.monitorType,
                    dataToProcess: input.dataToProcess,
                    monitor: input.monitor,
                    seriesLabels,
                });
                alert.title = MonitorTemplateUtil.processTemplateString({
                    value: criteriaAlert.title,
                    storageMap,
                });
                alert.description = MonitorTemplateUtil.processTemplateString({
                    value: criteriaAlert.description,
                    storageMap,
                });
                if (!criteriaAlert.alertSeverityId) {
                    // pick the critical criteria.
                    const severity = await AlertSeverityService.findOneBy({
                        query: {
                            projectId: input.monitor.projectId,
                        },
                        sort: {
                            order: SortOrder.Ascending,
                        },
                        props: {
                            isRoot: true,
                        },
                        select: {
                            _id: true,
                        },
                    });
                    if (!severity) {
                        throw new BadDataException("Project does not have alert severity");
                    }
                    else {
                        alert.alertSeverityId = severity.id;
                    }
                }
                else {
                    alert.alertSeverityId = criteriaAlert.alertSeverityId;
                }
                alert.monitor = input.monitor;
                alert.projectId = input.monitor.projectId;
                alert.rootCause = seriesRootCause;
                alert.createdStateLog = JSON.parse(JSON.stringify(input.dataToProcess, null, 2));
                alert.createdCriteriaId = input.criteriaInstance.data.id.toString();
                if (seriesFingerprint) {
                    alert.seriesFingerprint = seriesFingerprint;
                }
                if (seriesLabels && Object.keys(seriesLabels).length > 0) {
                    alert.seriesLabels = seriesLabels;
                    /*
                     * Link the alert to the Host that emitted this series, if
                     * the metric carried a `host.name` resource attribute. The
                     * Host record was auto-discovered during OTel ingestion.
                     * Metric attributes are stored with the `resource.` prefix in
                     * ClickHouse, so the group-by dropdown surfaces
                     * `resource.host.name` — but accept the bare `host.name` too
                     * for any non-OTel ingest paths.
                     */
                    const hostName = typeof seriesLabels["resource.host.name"] === "string"
                        ? seriesLabels["resource.host.name"]
                        : typeof seriesLabels["host.name"] === "string"
                            ? seriesLabels["host.name"]
                            : undefined;
                    if (hostName) {
                        const host = await HostService.findOneBy({
                            query: {
                                projectId: input.monitor.projectId,
                                hostIdentifier: hostName,
                            },
                            select: {
                                _id: true,
                            },
                            props: {
                                isRoot: true,
                            },
                        });
                        if (host) {
                            alert.hosts = [host];
                        }
                    }
                    /*
                     * Same idea for Service — OTel ingest stamps `service.name` and
                     * auto-creates a Service row keyed by that name (see
                     * OpenTelemetryIngestService.telemetryServiceFromName). When the
                     * breaching series carries that attribute, link the alert to the
                     * emitting service so the on-call/labels pipeline can fan out.
                     */
                    const serviceName = typeof seriesLabels["resource.service.name"] === "string"
                        ? seriesLabels["resource.service.name"]
                        : typeof seriesLabels["service.name"] === "string"
                            ? seriesLabels["service.name"]
                            : undefined;
                    if (serviceName) {
                        const service = await ServiceService.findOneBy({
                            query: {
                                projectId: input.monitor.projectId,
                                name: serviceName,
                            },
                            select: {
                                _id: true,
                            },
                            props: {
                                isRoot: true,
                            },
                        });
                        if (service) {
                            alert.services = [service];
                        }
                    }
                }
                alert.onCallDutyPolicies =
                    ((_r = criteriaAlert.onCallPolicyIds) === null || _r === void 0 ? void 0 : _r.map((id) => {
                        const onCallPolicy = new OnCallDutyPolicy();
                        onCallPolicy._id = id.toString();
                        return onCallPolicy;
                    })) || [];
                // Set labels from criteria
                alert.labels =
                    ((_s = criteriaAlert.labelIds) === null || _s === void 0 ? void 0 : _s.map((id) => {
                        const label = new Label();
                        label._id = id.toString();
                        return label;
                    })) || [];
                alert.isCreatedAutomatically = true;
                if (criteriaAlert.isPrivate === true) {
                    alert.isPrivate = true;
                }
                if (input.props.telemetryQuery) {
                    alert.telemetryQuery = input.props.telemetryQuery;
                }
                if (input.dataToProcess &&
                    input.dataToProcess.probeId) {
                    alert.createdByProbeId = input.dataToProcess.probeId;
                }
                if (criteriaAlert.remediationNotes) {
                    alert.remediationNotes = MonitorTemplateUtil.processTemplateString({
                        value: criteriaAlert.remediationNotes,
                        storageMap,
                    });
                }
                if (DisableAutomaticAlertCreation) {
                    (_t = input.evaluationSummary) === null || _t === void 0 ? void 0 : _t.events.push({
                        type: "alert-skipped",
                        title: "Alert creation skipped",
                        message: "Automatic alert creation is disabled by environment configuration.",
                        relatedCriteriaId: (_u = input.criteriaInstance.data) === null || _u === void 0 ? void 0 : _u.id,
                        at: OneUptimeDate.getCurrentDate(),
                    });
                    return;
                }
                const createdAlert = await AlertService.create({
                    data: alert,
                    props: {
                        isRoot: true,
                    },
                });
                // Add owner teams and users after alert creation
                if (((_v = criteriaAlert.ownerTeamIds) === null || _v === void 0 ? void 0 : _v.length) ||
                    ((_w = criteriaAlert.ownerUserIds) === null || _w === void 0 ? void 0 : _w.length)) {
                    await AlertService.addOwners(input.monitor.projectId, createdAlert.id, criteriaAlert.ownerUserIds || [], criteriaAlert.ownerTeamIds || [], true, // notify owners
                    {
                        isRoot: true,
                    });
                }
                (_x = input.evaluationSummary) === null || _x === void 0 ? void 0 : _x.events.push({
                    type: "alert-created",
                    title: `Alert created: ${createdAlert.title || criteriaAlert.title}`,
                    message: `Alert triggered from criteria "${((_y = input.criteriaInstance.data) === null || _y === void 0 ? void 0 : _y.name) || "Unnamed criteria"}".`,
                    relatedCriteriaId: (_z = input.criteriaInstance.data) === null || _z === void 0 ? void 0 : _z.id,
                    relatedAlertId: (_0 = createdAlert.id) === null || _0 === void 0 ? void 0 : _0.toString(),
                    relatedAlertNumber: createdAlert.alertNumber,
                    relatedAlertNumberWithPrefix: createdAlert.alertNumberWithPrefix,
                    at: OneUptimeDate.getCurrentDate(),
                });
            }
        }
    }
    static async resolveOpenAlert(input) {
        const resolvedStateId = await AlertStateTimelineService.getResolvedStateIdForProject(input.openAlert.projectId);
        const alertStateTimeline = new AlertStateTimeline();
        alertStateTimeline.alertId = input.openAlert.id;
        alertStateTimeline.alertStateId = resolvedStateId;
        alertStateTimeline.projectId = input.openAlert.projectId;
        if (input.rootCause) {
            alertStateTimeline.rootCause =
                "Alert autoresolved because autoresolve is set to true in monitor criteria. " +
                    input.rootCause;
        }
        if (input.dataToProcess) {
            alertStateTimeline.stateChangeLog = JSON.parse(JSON.stringify(input.dataToProcess));
        }
        await AlertStateTimelineService.create({
            data: alertStateTimeline,
            props: {
                isRoot: true,
            },
        });
    }
    static shouldCloseAlert(input) {
        var _a, _b, _c, _d, _e, _f;
        const openSeriesFingerprint = input.openAlert.seriesFingerprint || undefined;
        /*
         * Per-series auto-resolve: when a breaching-series set is given and
         * this alert has a fingerprint, resolve whenever the fingerprint is
         * no longer in the set, independent of whether other series on the
         * same monitor are still breaching.
         */
        if (input.breachingSeriesFingerprints !== undefined &&
            openSeriesFingerprint) {
            const stillBreaching = input.breachingSeriesFingerprints.has(openSeriesFingerprint);
            if (stillBreaching) {
                return false;
            }
            if (!((_a = input.openAlert.createdCriteriaId) === null || _a === void 0 ? void 0 : _a.toString())) {
                return false;
            }
            const autoResolveTemplates = input.autoResolveCriteriaInstanceIdAlertIdsDictionary[input.openAlert.createdCriteriaId.toString()];
            /*
             * Alert auto-resolve lists templates by criteria; presence of any
             * template for this criteria means "this criteria's alerts are
             * configured to auto-resolve", so resolve this series.
             */
            if (autoResolveTemplates && autoResolveTemplates.length > 0) {
                return true;
            }
            return false;
        }
        if (((_b = input.openAlert.createdCriteriaId) === null || _b === void 0 ? void 0 : _b.toString()) ===
            ((_d = (_c = input.criteriaInstance) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.id.toString())) {
            // same alert active. So, do not close.
            return false;
        }
        // If antoher criteria is active then, check if the alert id is present in the map.
        if (!((_e = input.openAlert.createdCriteriaId) === null || _e === void 0 ? void 0 : _e.toString())) {
            return false;
        }
        if (input.autoResolveCriteriaInstanceIdAlertIdsDictionary[(_f = input.openAlert.createdCriteriaId) === null || _f === void 0 ? void 0 : _f.toString()]) {
            return true;
        }
        return false;
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], MonitorAlert, "checkOpenAlertsAndCloseIfResolved", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], MonitorAlert, "criteriaMetCreateAlertsAndUpdateMonitorStatus", null);
//# sourceMappingURL=MonitorAlert.js.map