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 QueryHelper from "../Types/Database/QueryHelper";
import DatabaseService from "./DatabaseService";
import AlertService from "./AlertService";
import AlertStateService from "./AlertStateService";
import UserService from "./UserService";
import SortOrder from "../../Types/BaseDatabase/SortOrder";
import OneUptimeDate from "../../Types/Date";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import AlertStateTimeline from "../../Models/DatabaseModels/AlertStateTimeline";
import { IsBillingEnabled } from "../EnvironmentConfig";
import AlertInternalNote from "../../Models/DatabaseModels/AlertInternalNote";
import AlertInternalNoteService from "./AlertInternalNoteService";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import logger from "../Utils/Logger";
import AlertFeedService from "./AlertFeedService";
import { AlertFeedEventType } from "../../Models/DatabaseModels/AlertFeed";
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
import Semaphore from "../Infrastructure/Semaphore";
export class Service extends DatabaseService {
    constructor() {
        super(AlertStateTimeline);
        if (IsBillingEnabled) {
            this.hardDeleteItemsOlderThanInDays("createdAt", 3 * 365); // 3 years
        }
    }
    async getResolvedStateIdForProject(projectId) {
        const resolvedState = await AlertStateService.findOneBy({
            query: {
                projectId: projectId,
                isResolvedState: true,
            },
            props: {
                isRoot: true,
            },
            select: {
                _id: true,
            },
        });
        if (!resolvedState) {
            throw new BadDataException("No resolved state found for the project");
        }
        return resolvedState.id;
    }
    async onBeforeCreate(createBy) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        if (!createBy.data.alertId) {
            throw new BadDataException("alertId is null");
        }
        let mutex = null;
        try {
            if (!createBy.data.startsAt) {
                createBy.data.startsAt = OneUptimeDate.getCurrentDate();
            }
            try {
                mutex = await Semaphore.lock({
                    key: createBy.data.alertId.toString(),
                    namespace: "AlertStateTimeline.create",
                });
            }
            catch (err) {
                logger.error(err, {
                    projectId: (_a = createBy.data.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertId: (_b = createBy.data.alertId) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            }
            if ((createBy.data.createdByUserId ||
                createBy.data.createdByUser ||
                createBy.props.userId) &&
                !createBy.data.rootCause) {
                let userId = createBy.data.createdByUserId;
                if (createBy.props.userId) {
                    userId = createBy.props.userId;
                }
                if (createBy.data.createdByUser && createBy.data.createdByUser.id) {
                    userId = createBy.data.createdByUser.id;
                }
                if (userId) {
                    createBy.data.rootCause = `Alert state created by ${await UserService.getUserMarkdownString({
                        userId: userId,
                        projectId: createBy.data.projectId || createBy.props.tenantId,
                    })}`;
                }
            }
            const alertStateId = createBy.data.alertStateId || ((_c = createBy.data.alertState) === null || _c === void 0 ? void 0 : _c.id);
            if (!alertStateId) {
                throw new BadDataException("alertStateId is null");
            }
            const stateBeforeThis = await this.findOneBy({
                query: {
                    alertId: createBy.data.alertId,
                    startsAt: QueryHelper.lessThanEqualTo(createBy.data.startsAt),
                },
                sort: {
                    startsAt: SortOrder.Descending,
                },
                props: {
                    isRoot: true,
                },
                select: {
                    alertStateId: true,
                    alertState: {
                        order: true,
                        name: true,
                    },
                    startsAt: true,
                    endsAt: true,
                },
            });
            logger.debug("State Before this");
            logger.debug(stateBeforeThis);
            // If this is the first state, then do not notify the owner.
            if (!stateBeforeThis) {
                // since this is the first status, do not notify the owner.
                createBy.data.isOwnerNotified = true;
            }
            /*
             * check if this new state and the previous state are same.
             * if yes, then throw bad data exception.
             */
            if (stateBeforeThis && stateBeforeThis.alertStateId && alertStateId) {
                if (stateBeforeThis.alertStateId.toString() === alertStateId.toString()) {
                    throw new BadDataException("Alert state cannot be same as previous state.");
                }
            }
            if (stateBeforeThis && ((_d = stateBeforeThis.alertState) === null || _d === void 0 ? void 0 : _d.order)) {
                const newAlertState = await AlertStateService.findOneBy({
                    query: {
                        _id: alertStateId,
                    },
                    select: {
                        order: true,
                        name: true,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (newAlertState && newAlertState.order) {
                    // check if the new alert state is in order is greater than the previous state order
                    if (stateBeforeThis &&
                        stateBeforeThis.alertState &&
                        stateBeforeThis.alertState.order &&
                        newAlertState.order <= stateBeforeThis.alertState.order) {
                        throw new BadDataException(`Alert cannot transition to ${newAlertState.name} state from ${stateBeforeThis.alertState.name} state because ${newAlertState.name} is before ${stateBeforeThis.alertState.name} in the order of alert states.`);
                    }
                }
            }
            const stateAfterThis = await this.findOneBy({
                query: {
                    alertId: createBy.data.alertId,
                    startsAt: QueryHelper.greaterThan(createBy.data.startsAt),
                },
                sort: {
                    startsAt: SortOrder.Ascending,
                },
                props: {
                    isRoot: true,
                },
                select: {
                    alertStateId: true,
                    startsAt: true,
                    endsAt: true,
                },
            });
            // compute ends at. It's the start of the next status.
            if (stateAfterThis && stateAfterThis.startsAt) {
                createBy.data.endsAt = stateAfterThis.startsAt;
            }
            /*
             * check if this new state and the previous state are same.
             * if yes, then throw bad data exception.
             */
            if (stateAfterThis && stateAfterThis.alertStateId && alertStateId) {
                if (stateAfterThis.alertStateId.toString() === alertStateId.toString()) {
                    throw new BadDataException("Alert state cannot be same as next state.");
                }
            }
            logger.debug("State After this");
            logger.debug(stateAfterThis);
            const internalNote = (_e = createBy.miscDataProps) === null || _e === void 0 ? void 0 : _e["internalNote"];
            if (internalNote) {
                const alertNote = new AlertInternalNote();
                alertNote.alertId = createBy.data.alertId;
                alertNote.note = internalNote;
                alertNote.createdAt = createBy.data.startsAt;
                alertNote.projectId = createBy.data.projectId;
                await AlertInternalNoteService.create({
                    data: alertNote,
                    props: createBy.props,
                });
            }
            const privateNote = (_f = createBy.miscDataProps) === null || _f === void 0 ? void 0 : _f["privateNote"];
            return {
                createBy,
                carryForward: {
                    statusTimelineBeforeThisStatus: stateBeforeThis || null,
                    statusTimelineAfterThisStatus: stateAfterThis || null,
                    privateNote: privateNote,
                    mutex: mutex,
                },
            };
        }
        catch (error) {
            // release the mutex if it was acquired.
            if (mutex) {
                try {
                    await Semaphore.release(mutex);
                }
                catch (err) {
                    logger.error(err, {
                        projectId: (_g = createBy.data.projectId) === null || _g === void 0 ? void 0 : _g.toString(),
                        alertId: (_h = createBy.data.alertId) === null || _h === void 0 ? void 0 : _h.toString(),
                    });
                }
            }
            throw error;
        }
    }
    async onCreateSuccess(onCreate, createdItem) {
        var _a, _b, _c;
        if (!createdItem.alertId) {
            throw new BadDataException("alertId is null");
        }
        const mutex = onCreate.carryForward.mutex;
        if (!createdItem.alertStateId) {
            throw new BadDataException("alertStateId is null");
        }
        logger.debug("Status Timeline Before this");
        logger.debug(onCreate.carryForward.statusTimelineBeforeThisStatus);
        logger.debug("Status Timeline After this");
        logger.debug(onCreate.carryForward.statusTimelineAfterThisStatus);
        logger.debug("Created Item");
        logger.debug(createdItem);
        /*
         * now there are three cases.
         * 1. This is the first status OR there's no status after this.
         */
        if (!onCreate.carryForward.statusTimelineBeforeThisStatus) {
            // This is the first status, no need to update previous status.
            logger.debug("This is the first status.");
        }
        else if (!onCreate.carryForward.statusTimelineAfterThisStatus) {
            /*
             * 2. This is the last status.
             * Update the previous status to end at the start of this status.
             */
            await this.updateOneById({
                id: onCreate.carryForward.statusTimelineBeforeThisStatus.id,
                data: {
                    endsAt: createdItem.startsAt,
                },
                props: {
                    isRoot: true,
                },
            });
            logger.debug("This is the last status.");
        }
        else {
            /*
             * 3. This is in the middle.
             * Update the previous status to end at the start of this status.
             */
            await this.updateOneById({
                id: onCreate.carryForward.statusTimelineBeforeThisStatus.id,
                data: {
                    endsAt: createdItem.startsAt,
                },
                props: {
                    isRoot: true,
                },
            });
            // Update the next status to start at the end of this status.
            await this.updateOneById({
                id: onCreate.carryForward.statusTimelineAfterThisStatus.id,
                data: {
                    startsAt: createdItem.endsAt,
                },
                props: {
                    isRoot: true,
                },
            });
            logger.debug("This status is in the middle.");
        }
        if (!createdItem.endsAt) {
            await AlertService.updateOneBy({
                query: {
                    _id: (_a = createdItem.alertId) === null || _a === void 0 ? void 0 : _a.toString(),
                },
                data: {
                    currentAlertStateId: createdItem.alertStateId,
                },
                props: onCreate.createBy.props,
            });
        }
        if (mutex) {
            try {
                await Semaphore.release(mutex);
            }
            catch (err) {
                logger.error(err, {
                    projectId: (_b = createdItem.projectId) === null || _b === void 0 ? void 0 : _b.toString(),
                    alertId: (_c = createdItem.alertId) === null || _c === void 0 ? void 0 : _c.toString(),
                });
            }
        }
        const alertState = await AlertStateService.findOneBy({
            query: {
                _id: createdItem.alertStateId.toString(),
            },
            props: {
                isRoot: true,
            },
            select: {
                _id: true,
                isResolvedState: true,
                isAcknowledgedState: true,
                isCreatedState: true,
                color: true,
                name: true,
            },
        });
        const stateName = (alertState === null || alertState === void 0 ? void 0 : alertState.name) || "";
        let stateEmoji = "➡️";
        // if resolved state then change emoji to ✅.
        if (alertState === null || alertState === void 0 ? void 0 : alertState.isResolvedState) {
            stateEmoji = "✅";
        }
        else if (alertState === null || alertState === void 0 ? void 0 : alertState.isAcknowledgedState) {
            // eyes emoji for acknowledged state.
            stateEmoji = "👀";
        }
        else if (alertState === null || alertState === void 0 ? void 0 : alertState.isCreatedState) {
            stateEmoji = "🔴";
        }
        const alertNumberResult = await AlertService.getAlertNumber({
            alertId: createdItem.alertId,
        });
        const projectId = createdItem.projectId;
        const alertId = createdItem.alertId;
        await AlertFeedService.createAlertFeedItem({
            alertId: createdItem.alertId,
            projectId: createdItem.projectId,
            alertFeedEventType: AlertFeedEventType.AlertStateChanged,
            displayColor: alertState === null || alertState === void 0 ? void 0 : alertState.color,
            feedInfoInMarkdown: stateEmoji +
                ` Changed **[Alert ${alertNumberResult.numberWithPrefix || "#" + alertNumberResult.number}](${(await AlertService.getAlertLinkInDashboard(projectId, alertId)).toString()}) State** to **` +
                stateName +
                "**",
            moreInformationInMarkdown: `**Cause:** 
${createdItem.rootCause}`,
            userId: createdItem.createdByUserId || onCreate.createBy.props.userId,
            workspaceNotification: {
                sendWorkspaceNotification: true,
                notifyUserId: createdItem.createdByUserId || onCreate.createBy.props.userId,
            },
        });
        if (onCreate.carryForward.privateNote) {
            const privateNote = onCreate.carryForward.privateNote;
            const alertInternalNote = new AlertInternalNote();
            alertInternalNote.alertId = createdItem.alertId;
            alertInternalNote.note = privateNote;
            alertInternalNote.createdAt = createdItem.startsAt;
            alertInternalNote.projectId = createdItem.projectId;
            await AlertInternalNoteService.create({
                data: alertInternalNote,
                props: onCreate.createBy.props,
            });
        }
        AlertService.refreshAlertMetrics({
            alertId: createdItem.alertId,
        }).catch((error) => {
            var _a, _b, _c, _d;
            logger.error("Error while refreshing alert metrics after alert state timeline creation", {
                projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                alertId: (_b = createdItem.alertId) === null || _b === void 0 ? void 0 : _b.toString(),
            });
            logger.error(error, {
                projectId: (_c = createdItem.projectId) === null || _c === void 0 ? void 0 : _c.toString(),
                alertId: (_d = createdItem.alertId) === null || _d === void 0 ? void 0 : _d.toString(),
            });
        });
        const isLastAlertState = await this.isLastAlertState({
            projectId: createdItem.projectId,
            alertStateId: createdItem.alertStateId,
        });
        if (isLastAlertState) {
            WorkspaceNotificationRuleService.archiveWorkspaceChannels({
                projectId: createdItem.projectId,
                notificationFor: {
                    alertId: createdItem.alertId,
                },
                sendMessageBeforeArchiving: {
                    _type: "WorkspacePayloadMarkdown",
                    text: `**[Alert ${alertNumberResult.numberWithPrefix || "#" + alertNumberResult.number}](${(await AlertService.getAlertLinkInDashboard(createdItem.projectId, createdItem.alertId)).toString()})** is resolved. Archiving channel.`,
                },
            }).catch((error) => {
                var _a, _b, _c, _d;
                logger.error(`Error while archiving workspace channels:`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertId: (_b = createdItem.alertId) === null || _b === void 0 ? void 0 : _b.toString(),
                });
                logger.error(error, {
                    projectId: (_c = createdItem.projectId) === null || _c === void 0 ? void 0 : _c.toString(),
                    alertId: (_d = createdItem.alertId) === null || _d === void 0 ? void 0 : _d.toString(),
                });
            });
        }
        return createdItem;
    }
    async isLastAlertState(data) {
        var _a;
        // find all the states for this project and sort it by order. Then, check if this is the last state.
        const alertStates = await AlertStateService.findBy({
            query: {
                projectId: data.projectId,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
            sort: {
                order: SortOrder.Ascending,
            },
            props: {
                isRoot: true,
            },
        });
        const alertState = alertStates.find((alertState) => {
            var _a;
            return ((_a = alertState.id) === null || _a === void 0 ? void 0 : _a.toString()) === data.alertStateId.toString();
        }) || null;
        if (!alertState) {
            throw new BadDataException("Alert state not found.");
        }
        const lastAlertState = alertStates[alertStates.length - 1];
        if (lastAlertState && lastAlertState.id) {
            return lastAlertState.id.toString() === ((_a = alertState.id) === null || _a === void 0 ? void 0 : _a.toString());
        }
        return false;
    }
    async onBeforeDelete(deleteBy) {
        if (deleteBy.query._id) {
            const alertStateTimelineToBeDeleted = await this.findOneById({
                id: new ObjectID(deleteBy.query._id),
                select: {
                    alertId: true,
                    startsAt: true,
                    endsAt: true,
                },
                props: {
                    isRoot: true,
                },
            });
            const alertId = alertStateTimelineToBeDeleted === null || alertStateTimelineToBeDeleted === void 0 ? void 0 : alertStateTimelineToBeDeleted.alertId;
            if (alertId) {
                const alertStateTimeline = await this.countBy({
                    query: {
                        alertId: alertId,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (!alertStateTimelineToBeDeleted) {
                    throw new BadDataException("Alert state timeline not found.");
                }
                if (alertStateTimeline.isOne()) {
                    throw new BadDataException("Cannot delete the only state timeline. Alert should have at least one state in its timeline.");
                }
                /*
                 * There are three cases.
                 * 1. This is the first state.
                 * 2. This is the last state.
                 * 3. This is in the middle.
                 */
                const stateBeforeThis = await this.findOneBy({
                    query: {
                        _id: QueryHelper.notEquals(deleteBy.query._id),
                        alertId: alertId,
                        startsAt: QueryHelper.lessThanEqualTo(alertStateTimelineToBeDeleted.startsAt),
                    },
                    sort: {
                        startsAt: SortOrder.Descending,
                    },
                    props: {
                        isRoot: true,
                    },
                    select: {
                        alertStateId: true,
                        startsAt: true,
                        endsAt: true,
                    },
                });
                const stateAfterThis = await this.findOneBy({
                    query: {
                        alertId: alertId,
                        startsAt: QueryHelper.greaterThan(alertStateTimelineToBeDeleted.startsAt),
                    },
                    sort: {
                        startsAt: SortOrder.Ascending,
                    },
                    props: {
                        isRoot: true,
                    },
                    select: {
                        alertStateId: true,
                        startsAt: true,
                        endsAt: true,
                    },
                });
                if (!stateBeforeThis) {
                    // This is the first state, no need to update previous state.
                    logger.debug("This is the first state.");
                }
                else if (!stateAfterThis) {
                    /*
                     * This is the last state.
                     * Update the previous state to end at the end of this state.
                     */
                    await this.updateOneById({
                        id: stateBeforeThis.id,
                        data: {
                            endsAt: alertStateTimelineToBeDeleted.endsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    logger.debug("This is the last state.");
                }
                else {
                    /*
                     * This state is in the middle.
                     * Update the previous state to end at the start of the next state.
                     */
                    await this.updateOneById({
                        id: stateBeforeThis.id,
                        data: {
                            endsAt: stateAfterThis.startsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    // Update the next state to start at the start of this state.
                    await this.updateOneById({
                        id: stateAfterThis.id,
                        data: {
                            startsAt: alertStateTimelineToBeDeleted.startsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    logger.debug("This state is in the middle.");
                }
            }
            return { deleteBy, carryForward: alertId };
        }
        return { deleteBy, carryForward: null };
    }
    async onDeleteSuccess(onDelete, _itemIdsBeforeDelete) {
        if (onDelete.carryForward) {
            // this is alertId.
            const alertId = onDelete.carryForward;
            // get last status of this alert.
            const alertStateTimeline = await this.findOneBy({
                query: {
                    alertId: alertId,
                },
                sort: {
                    startsAt: SortOrder.Descending,
                },
                props: {
                    isRoot: true,
                },
                select: {
                    _id: true,
                    alertStateId: true,
                },
            });
            if (alertStateTimeline && alertStateTimeline.alertStateId) {
                await AlertService.updateOneBy({
                    query: {
                        _id: alertId.toString(),
                    },
                    data: {
                        currentAlertStateId: alertStateTimeline.alertStateId,
                    },
                    props: {
                        isRoot: true,
                    },
                });
            }
        }
        return onDelete;
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getResolvedStateIdForProject", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeCreate", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, AlertStateTimeline]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onCreateSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeDelete", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Array]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onDeleteSuccess", null);
export default new Service();
//# sourceMappingURL=AlertStateTimelineService.js.map