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 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 AlertEpisodeStateTimeline from "../../Models/DatabaseModels/AlertEpisodeStateTimeline";
import { IsBillingEnabled } from "../EnvironmentConfig";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import logger from "../Utils/Logger";
import AlertEpisodeFeedService from "./AlertEpisodeFeedService";
import { AlertEpisodeFeedEventType } from "../../Models/DatabaseModels/AlertEpisodeFeed";
import Semaphore from "../Infrastructure/Semaphore";
import AlertEpisodeService from "./AlertEpisodeService";
export class Service extends DatabaseService {
    constructor() {
        super(AlertEpisodeStateTimeline);
        if (IsBillingEnabled) {
            this.hardDeleteItemsOlderThanInDays("createdAt", 3 * 365); // 3 years
        }
    }
    async onBeforeCreate(createBy) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
        if (!createBy.data.alertEpisodeId) {
            throw new BadDataException("alertEpisodeId is null");
        }
        let mutex = null;
        try {
            if (!createBy.data.startsAt) {
                createBy.data.startsAt = OneUptimeDate.getCurrentDate();
            }
            try {
                mutex = await Semaphore.lock({
                    key: createBy.data.alertEpisodeId.toString(),
                    namespace: "AlertEpisodeStateTimeline.create",
                });
            }
            catch (err) {
                logger.error(err, {
                    projectId: (_a = createBy.data.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    alertEpisodeId: (_b = createBy.data.alertEpisodeId) === 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 = `Episode 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: {
                    alertEpisodeId: createBy.data.alertEpisodeId,
                    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", {
                projectId: (_d = createBy.data.projectId) === null || _d === void 0 ? void 0 : _d.toString(),
                alertEpisodeId: (_e = createBy.data.alertEpisodeId) === null || _e === void 0 ? void 0 : _e.toString(),
            });
            logger.debug(stateBeforeThis, {
                projectId: (_f = createBy.data.projectId) === null || _f === void 0 ? void 0 : _f.toString(),
                alertEpisodeId: (_g = createBy.data.alertEpisodeId) === null || _g === void 0 ? void 0 : _g.toString(),
            });
            // 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 (stateBeforeThis && stateBeforeThis.alertStateId && alertStateId) {
                if (stateBeforeThis.alertStateId.toString() === alertStateId.toString()) {
                    throw new BadDataException("Episode state cannot be same as previous state.");
                }
            }
            const stateAfterThis = await this.findOneBy({
                query: {
                    alertEpisodeId: createBy.data.alertEpisodeId,
                    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 next state are same.
            if (stateAfterThis && stateAfterThis.alertStateId && alertStateId) {
                if (stateAfterThis.alertStateId.toString() === alertStateId.toString()) {
                    throw new BadDataException("Episode state cannot be same as next state.");
                }
            }
            logger.debug("State After this", {
                projectId: (_h = createBy.data.projectId) === null || _h === void 0 ? void 0 : _h.toString(),
                alertEpisodeId: (_j = createBy.data.alertEpisodeId) === null || _j === void 0 ? void 0 : _j.toString(),
            });
            logger.debug(stateAfterThis, {
                projectId: (_k = createBy.data.projectId) === null || _k === void 0 ? void 0 : _k.toString(),
                alertEpisodeId: (_l = createBy.data.alertEpisodeId) === null || _l === void 0 ? void 0 : _l.toString(),
            });
            return {
                createBy,
                carryForward: {
                    statusTimelineBeforeThisStatus: stateBeforeThis || null,
                    statusTimelineAfterThisStatus: stateAfterThis || null,
                    mutex: mutex,
                },
            };
        }
        catch (error) {
            // release the mutex if it was acquired.
            if (mutex) {
                try {
                    await Semaphore.release(mutex);
                }
                catch (err) {
                    logger.error(err, {
                        projectId: (_m = createBy.data.projectId) === null || _m === void 0 ? void 0 : _m.toString(),
                        alertEpisodeId: (_o = createBy.data.alertEpisodeId) === null || _o === void 0 ? void 0 : _o.toString(),
                    });
                }
            }
            throw error;
        }
    }
    async onCreateSuccess(onCreate, createdItem) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
        if (!createdItem.alertEpisodeId) {
            throw new BadDataException("alertEpisodeId is null");
        }
        const mutex = onCreate.carryForward.mutex;
        if (!createdItem.alertStateId) {
            throw new BadDataException("alertStateId is null");
        }
        logger.debug("Status Timeline Before this", {
            projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
            alertEpisodeId: (_b = createdItem.alertEpisodeId) === null || _b === void 0 ? void 0 : _b.toString(),
        });
        logger.debug(onCreate.carryForward.statusTimelineBeforeThisStatus, {
            projectId: (_c = createdItem.projectId) === null || _c === void 0 ? void 0 : _c.toString(),
            alertEpisodeId: (_d = createdItem.alertEpisodeId) === null || _d === void 0 ? void 0 : _d.toString(),
        });
        logger.debug("Status Timeline After this", {
            projectId: (_e = createdItem.projectId) === null || _e === void 0 ? void 0 : _e.toString(),
            alertEpisodeId: (_f = createdItem.alertEpisodeId) === null || _f === void 0 ? void 0 : _f.toString(),
        });
        logger.debug(onCreate.carryForward.statusTimelineAfterThisStatus, {
            projectId: (_g = createdItem.projectId) === null || _g === void 0 ? void 0 : _g.toString(),
            alertEpisodeId: (_h = createdItem.alertEpisodeId) === null || _h === void 0 ? void 0 : _h.toString(),
        });
        logger.debug("Created Item", {
            projectId: (_j = createdItem.projectId) === null || _j === void 0 ? void 0 : _j.toString(),
            alertEpisodeId: (_k = createdItem.alertEpisodeId) === null || _k === void 0 ? void 0 : _k.toString(),
        });
        logger.debug(createdItem, {
            projectId: (_l = createdItem.projectId) === null || _l === void 0 ? void 0 : _l.toString(),
            alertEpisodeId: (_m = createdItem.alertEpisodeId) === null || _m === void 0 ? void 0 : _m.toString(),
        });
        // Handle timeline updates
        if (!onCreate.carryForward.statusTimelineBeforeThisStatus) {
            // This is the first status, no need to update previous status.
            logger.debug("This is the first status.", {
                projectId: (_o = createdItem.projectId) === null || _o === void 0 ? void 0 : _o.toString(),
                alertEpisodeId: (_p = createdItem.alertEpisodeId) === null || _p === void 0 ? void 0 : _p.toString(),
            });
        }
        else if (!onCreate.carryForward.statusTimelineAfterThisStatus) {
            // 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.", {
                projectId: (_q = createdItem.projectId) === null || _q === void 0 ? void 0 : _q.toString(),
                alertEpisodeId: (_r = createdItem.alertEpisodeId) === null || _r === void 0 ? void 0 : _r.toString(),
            });
        }
        else {
            // 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.", {
                projectId: (_s = createdItem.projectId) === null || _s === void 0 ? void 0 : _s.toString(),
                alertEpisodeId: (_t = createdItem.alertEpisodeId) === null || _t === void 0 ? void 0 : _t.toString(),
            });
        }
        // Update episode's current state if this is the latest timeline entry
        if (!createdItem.endsAt) {
            const updateData = {
                currentAlertStateId: createdItem.alertStateId,
            };
            // Check if the new state is a resolved state and update resolvedAt accordingly
            const newAlertState = await AlertStateService.findOneById({
                id: createdItem.alertStateId,
                select: {
                    isResolvedState: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (newAlertState === null || newAlertState === void 0 ? void 0 : newAlertState.isResolvedState) {
                // Set resolvedAt when transitioning to resolved state
                updateData.resolvedAt = OneUptimeDate.getCurrentDate();
            }
            else {
                // Clear resolvedAt when transitioning away from resolved state
                updateData.resolvedAt = null;
            }
            await AlertEpisodeService.updateOneBy({
                query: {
                    _id: (_u = createdItem.alertEpisodeId) === null || _u === void 0 ? void 0 : _u.toString(),
                },
                data: updateData,
                props: onCreate.createBy.props,
            });
            // Cascade state change to all member alerts
            if (createdItem.projectId) {
                try {
                    await AlertEpisodeService.cascadeStateToMemberAlerts({
                        projectId: createdItem.projectId,
                        episodeId: createdItem.alertEpisodeId,
                        alertStateId: createdItem.alertStateId,
                        props: {
                            isRoot: true,
                        },
                    });
                }
                catch (error) {
                    logger.error(`Failed to cascade state change to member alerts: ${error}`, {
                        projectId: (_v = createdItem.projectId) === null || _v === void 0 ? void 0 : _v.toString(),
                        alertEpisodeId: (_w = createdItem.alertEpisodeId) === null || _w === void 0 ? void 0 : _w.toString(),
                    });
                }
            }
        }
        if (mutex) {
            try {
                await Semaphore.release(mutex);
            }
            catch (err) {
                logger.error(err, {
                    projectId: (_x = createdItem.projectId) === null || _x === void 0 ? void 0 : _x.toString(),
                    alertEpisodeId: (_y = createdItem.alertEpisodeId) === null || _y === void 0 ? void 0 : _y.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 (alertState === null || alertState === void 0 ? void 0 : alertState.isResolvedState) {
            stateEmoji = "✅";
        }
        else if (alertState === null || alertState === void 0 ? void 0 : alertState.isAcknowledgedState) {
            stateEmoji = "👀";
        }
        else if (alertState === null || alertState === void 0 ? void 0 : alertState.isCreatedState) {
            stateEmoji = "🔴";
        }
        const episode = await AlertEpisodeService.findOneById({
            id: createdItem.alertEpisodeId,
            select: {
                episodeNumber: true,
                episodeNumberWithPrefix: true,
            },
            props: {
                isRoot: true,
            },
        });
        const episodeDisplayNumber = (episode === null || episode === void 0 ? void 0 : episode.episodeNumberWithPrefix) || "#" + ((episode === null || episode === void 0 ? void 0 : episode.episodeNumber) || 0);
        await AlertEpisodeFeedService.createAlertEpisodeFeedItem({
            alertEpisodeId: createdItem.alertEpisodeId,
            projectId: createdItem.projectId,
            alertEpisodeFeedEventType: AlertEpisodeFeedEventType.EpisodeStateChanged,
            displayColor: alertState === null || alertState === void 0 ? void 0 : alertState.color,
            feedInfoInMarkdown: stateEmoji +
                ` Changed **Episode ${episodeDisplayNumber} State** to **` +
                stateName +
                "**",
            moreInformationInMarkdown: createdItem.rootCause
                ? `**Cause:** \n${createdItem.rootCause}`
                : undefined,
            userId: createdItem.createdByUserId || onCreate.createBy.props.userId,
            workspaceNotification: {
                sendWorkspaceNotification: true,
                notifyUserId: createdItem.createdByUserId || onCreate.createBy.props.userId,
            },
        });
        return createdItem;
    }
    async onBeforeDelete(deleteBy) {
        if (deleteBy.query._id) {
            const episodeStateTimelineToBeDeleted = await this.findOneById({
                id: new ObjectID(deleteBy.query._id),
                select: {
                    alertEpisodeId: true,
                    startsAt: true,
                    endsAt: true,
                },
                props: {
                    isRoot: true,
                },
            });
            const episodeId = episodeStateTimelineToBeDeleted === null || episodeStateTimelineToBeDeleted === void 0 ? void 0 : episodeStateTimelineToBeDeleted.alertEpisodeId;
            if (episodeId) {
                const episodeStateTimeline = await this.countBy({
                    query: {
                        alertEpisodeId: episodeId,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (!episodeStateTimelineToBeDeleted) {
                    throw new BadDataException("Episode state timeline not found.");
                }
                if (episodeStateTimeline.isOne()) {
                    throw new BadDataException("Cannot delete the only state timeline. Episode should have at least one state in its timeline.");
                }
                // Handle timeline adjustments
                const stateBeforeThis = await this.findOneBy({
                    query: {
                        _id: QueryHelper.notEquals(deleteBy.query._id),
                        alertEpisodeId: episodeId,
                        startsAt: QueryHelper.lessThanEqualTo(episodeStateTimelineToBeDeleted.startsAt),
                    },
                    sort: {
                        startsAt: SortOrder.Descending,
                    },
                    props: {
                        isRoot: true,
                    },
                    select: {
                        alertStateId: true,
                        startsAt: true,
                        endsAt: true,
                    },
                });
                const stateAfterThis = await this.findOneBy({
                    query: {
                        alertEpisodeId: episodeId,
                        startsAt: QueryHelper.greaterThan(episodeStateTimelineToBeDeleted.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.", {
                        alertEpisodeId: episodeId === null || episodeId === void 0 ? void 0 : episodeId.toString(),
                    });
                }
                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: episodeStateTimelineToBeDeleted.endsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    logger.debug("This is the last state.", {
                        alertEpisodeId: episodeId === null || episodeId === void 0 ? void 0 : episodeId.toString(),
                    });
                }
                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: episodeStateTimelineToBeDeleted.startsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    logger.debug("This state is in the middle.", {
                        alertEpisodeId: episodeId === null || episodeId === void 0 ? void 0 : episodeId.toString(),
                    });
                }
            }
            return { deleteBy, carryForward: episodeId };
        }
        return { deleteBy, carryForward: null };
    }
    async onDeleteSuccess(onDelete, _itemIdsBeforeDelete) {
        if (onDelete.carryForward) {
            const episodeId = onDelete.carryForward;
            // Get last status of this episode.
            const episodeStateTimeline = await this.findOneBy({
                query: {
                    alertEpisodeId: episodeId,
                },
                sort: {
                    startsAt: SortOrder.Descending,
                },
                props: {
                    isRoot: true,
                },
                select: {
                    _id: true,
                    alertStateId: true,
                },
            });
            if (episodeStateTimeline && episodeStateTimeline.alertStateId) {
                await AlertEpisodeService.updateOneBy({
                    query: {
                        _id: episodeId.toString(),
                    },
                    data: {
                        currentAlertStateId: episodeStateTimeline.alertStateId,
                    },
                    props: {
                        isRoot: true,
                    },
                });
            }
        }
        return onDelete;
    }
}
__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, AlertEpisodeStateTimeline]),
    __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=AlertEpisodeStateTimelineService.js.map