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 Semaphore from "../Infrastructure/Semaphore";
import QueryHelper from "../Types/Database/QueryHelper";
import logger from "../Utils/Logger";
import DatabaseService from "./DatabaseService";
import MonitorService from "./MonitorService";
import UserService from "./UserService";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import SortOrder from "../../Types/BaseDatabase/SortOrder";
import OneUptimeDate from "../../Types/Date";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import MonitorStatusTimeline from "../../Models/DatabaseModels/MonitorStatusTimeline";
import MonitorFeedService from "./MonitorFeedService";
import { MonitorFeedEventType } from "../../Models/DatabaseModels/MonitorFeed";
import MonitorStatusService from "./MonitorStatusService";
export class Service extends DatabaseService {
    constructor() {
        super(MonitorStatusTimeline);
    }
    async onBeforeCreate(createBy) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
        if (!createBy.data.monitorId) {
            throw new BadDataException("monitorId is null");
        }
        let mutex = null;
        try {
            mutex = await Semaphore.lock({
                key: createBy.data.monitorId.toString(),
                namespace: "MonitorStatusTimeline.create",
            });
        }
        catch (e) {
            logger.error(e, {
                projectId: (_a = createBy.data.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                monitorId: (_b = createBy.data.monitorId) === null || _b === void 0 ? void 0 : _b.toString(),
            });
        }
        if (!createBy.data.startsAt) {
            createBy.data.startsAt = OneUptimeDate.getCurrentDate();
        }
        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 = `Monitor status created by ${await UserService.getUserMarkdownString({
                    userId: userId,
                    projectId: createBy.data.projectId || createBy.props.tenantId,
                })}`;
            }
        }
        const monitorStatusId = createBy.data.monitorStatusId || ((_c = createBy.data.monitorStatus) === null || _c === void 0 ? void 0 : _c.id);
        if (!monitorStatusId) {
            throw new BadDataException("monitorStatusId is null");
        }
        const stateBeforeThis = await this.findOneBy({
            query: {
                monitorId: createBy.data.monitorId,
                startsAt: QueryHelper.lessThanEqualTo(createBy.data.startsAt),
            },
            sort: {
                startsAt: SortOrder.Descending,
            },
            props: {
                isRoot: true,
            },
            select: {
                monitorStatusId: true,
                startsAt: true,
                endsAt: true,
            },
        });
        logger.debug("State Before this", {
            projectId: (_d = createBy.data.projectId) === null || _d === void 0 ? void 0 : _d.toString(),
            monitorId: (_e = createBy.data.monitorId) === null || _e === void 0 ? void 0 : _e.toString(),
        });
        logger.debug(stateBeforeThis, {
            projectId: (_f = createBy.data.projectId) === null || _f === void 0 ? void 0 : _f.toString(),
            monitorId: (_g = createBy.data.monitorId) === 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 yes, then throw bad data exception.
         */
        if (stateBeforeThis && stateBeforeThis.monitorStatusId && monitorStatusId) {
            if (stateBeforeThis.monitorStatusId.toString() ===
                monitorStatusId.toString()) {
                throw new BadDataException("Monitor Status cannot be same as previous status.");
            }
        }
        const stateAfterThis = await this.findOneBy({
            query: {
                monitorId: createBy.data.monitorId,
                startsAt: QueryHelper.greaterThan(createBy.data.startsAt),
            },
            sort: {
                startsAt: SortOrder.Ascending,
            },
            props: {
                isRoot: true,
            },
            select: {
                monitorStatusId: 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.monitorStatusId && monitorStatusId) {
            if (stateAfterThis.monitorStatusId.toString() === monitorStatusId.toString()) {
                throw new BadDataException("Monitor Status cannot be same as next status.");
            }
        }
        logger.debug("State After this", {
            projectId: (_h = createBy.data.projectId) === null || _h === void 0 ? void 0 : _h.toString(),
            monitorId: (_j = createBy.data.monitorId) === null || _j === void 0 ? void 0 : _j.toString(),
        });
        logger.debug(stateAfterThis, {
            projectId: (_k = createBy.data.projectId) === null || _k === void 0 ? void 0 : _k.toString(),
            monitorId: (_l = createBy.data.monitorId) === null || _l === void 0 ? void 0 : _l.toString(),
        });
        return {
            createBy,
            carryForward: {
                statusTimelineBeforeThisStatus: stateBeforeThis || null,
                statusTimelineAfterThisStatus: stateAfterThis || null,
                mutex: mutex,
            },
        };
    }
    async onCreateSuccess(onCreate, createdItem) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
        if (!createdItem.monitorId) {
            throw new BadDataException("monitorId is null");
        }
        if (!createdItem.monitorStatusId) {
            throw new BadDataException("monitorStatusId is null");
        }
        // update the last status as ended.
        logger.debug("Status Timeline Before this", {
            projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
            monitorId: (_b = createdItem.monitorId) === 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(),
            monitorId: (_d = createdItem.monitorId) === 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(),
            monitorId: (_f = createdItem.monitorId) === 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(),
            monitorId: (_h = createdItem.monitorId) === null || _h === void 0 ? void 0 : _h.toString(),
        });
        logger.debug("Created Item", {
            projectId: (_j = createdItem.projectId) === null || _j === void 0 ? void 0 : _j.toString(),
            monitorId: (_k = createdItem.monitorId) === null || _k === void 0 ? void 0 : _k.toString(),
        });
        logger.debug(createdItem, {
            projectId: (_l = createdItem.projectId) === null || _l === void 0 ? void 0 : _l.toString(),
            monitorId: (_m = createdItem.monitorId) === null || _m === void 0 ? void 0 : _m.toString(),
        });
        /*
         * 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.", {
                projectId: (_o = createdItem.projectId) === null || _o === void 0 ? void 0 : _o.toString(),
                monitorId: (_p = createdItem.monitorId) === null || _p === void 0 ? void 0 : _p.toString(),
            });
        }
        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.", {
                projectId: (_q = createdItem.projectId) === null || _q === void 0 ? void 0 : _q.toString(),
                monitorId: (_r = createdItem.monitorId) === null || _r === void 0 ? void 0 : _r.toString(),
            });
        }
        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.", {
                projectId: (_s = createdItem.projectId) === null || _s === void 0 ? void 0 : _s.toString(),
                monitorId: (_t = createdItem.monitorId) === null || _t === void 0 ? void 0 : _t.toString(),
            });
        }
        if (!createdItem.endsAt) {
            // if this is the last status, then update the monitor status.
            await MonitorService.updateOneBy({
                query: {
                    _id: (_u = createdItem.monitorId) === null || _u === void 0 ? void 0 : _u.toString(),
                },
                data: {
                    currentMonitorStatusId: createdItem.monitorStatusId,
                },
                props: onCreate.createBy.props,
            });
        }
        if (onCreate.carryForward.mutex) {
            const mutex = onCreate.carryForward.mutex;
            await Semaphore.release(mutex);
        }
        const monitorStatus = await MonitorStatusService.findOneBy({
            query: {
                _id: createdItem.monitorStatusId.toString(),
            },
            props: {
                isRoot: true,
            },
            select: {
                _id: true,
                isOfflineState: true,
                isOperationalState: true,
                color: true,
                name: true,
            },
        });
        const stateName = (monitorStatus === null || monitorStatus === void 0 ? void 0 : monitorStatus.name) || "";
        let stateEmoji = "➡️";
        // if resolved state then change emoji to 🟢.
        if (monitorStatus === null || monitorStatus === void 0 ? void 0 : monitorStatus.isOperationalState) {
            stateEmoji = "🟢";
        }
        else if (monitorStatus === null || monitorStatus === void 0 ? void 0 : monitorStatus.isOfflineState) {
            stateEmoji = "🔴";
        }
        const monitorName = await MonitorService.getMonitorName({
            monitorId: createdItem.monitorId,
        });
        const projectId = createdItem.projectId;
        const monitorId = createdItem.monitorId;
        await MonitorFeedService.createMonitorFeedItem({
            monitorId: createdItem.monitorId,
            projectId: createdItem.projectId,
            monitorFeedEventType: MonitorFeedEventType.MonitorStatusChanged,
            displayColor: monitorStatus === null || monitorStatus === void 0 ? void 0 : monitorStatus.color,
            feedInfoInMarkdown: stateEmoji +
                ` Changed Monitor **[${monitorName}](${(await MonitorService.getMonitorLinkInDashboard(projectId, monitorId)).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 ||
                    undefined,
            },
        });
        return createdItem;
    }
    async onBeforeDelete(deleteBy) {
        if (deleteBy.query._id) {
            const monitorStatusTimelineToBeDeleted = await this.findOneById({
                id: new ObjectID(deleteBy.query._id),
                select: {
                    monitorId: true,
                    startsAt: true,
                    endsAt: true,
                },
                props: {
                    isRoot: true,
                },
            });
            const monitorId = monitorStatusTimelineToBeDeleted === null || monitorStatusTimelineToBeDeleted === void 0 ? void 0 : monitorStatusTimelineToBeDeleted.monitorId;
            if (monitorId) {
                const monitorStatusTimeline = await this.countBy({
                    query: {
                        monitorId: monitorId,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (!monitorStatusTimelineToBeDeleted) {
                    throw new BadDataException("Monitor status timeline not found.");
                }
                if (monitorStatusTimeline.isOne()) {
                    throw new BadDataException("Cannot delete the only status timeline. Monitor should have at least one status timeline.");
                }
                /*
                 * There are three cases.
                 * 1. This is the first status.
                 * 2. This is the last status.
                 * 3. This is in the middle.
                 */
                const stateBeforeThis = await this.findOneBy({
                    query: {
                        _id: QueryHelper.notEquals(deleteBy.query._id),
                        monitorId: monitorId,
                        startsAt: QueryHelper.lessThanEqualTo(monitorStatusTimelineToBeDeleted.startsAt),
                    },
                    sort: {
                        startsAt: SortOrder.Descending,
                    },
                    props: {
                        isRoot: true,
                    },
                    select: {
                        monitorStatusId: true,
                        startsAt: true,
                        endsAt: true,
                    },
                });
                const stateAfterThis = await this.findOneBy({
                    query: {
                        monitorId: monitorId,
                        startsAt: QueryHelper.greaterThan(monitorStatusTimelineToBeDeleted.startsAt),
                    },
                    sort: {
                        startsAt: SortOrder.Ascending,
                    },
                    props: {
                        isRoot: true,
                    },
                    select: {
                        monitorStatusId: true,
                        startsAt: true,
                        endsAt: true,
                    },
                });
                if (!stateBeforeThis) {
                    // This is the first status, no need to update previous status.
                    logger.debug("This is the first status.", {
                        monitorId: monitorId === null || monitorId === void 0 ? void 0 : monitorId.toString(),
                    });
                }
                else if (!stateAfterThis) {
                    /*
                     * This is the last status.
                     * Update the previous status to end at the start of this status.
                     */
                    await this.updateOneById({
                        id: stateBeforeThis.id,
                        data: {
                            endsAt: monitorStatusTimelineToBeDeleted.endsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    logger.debug("This is the last status.", {
                        monitorId: monitorId === null || monitorId === void 0 ? void 0 : monitorId.toString(),
                    });
                }
                else {
                    /*
                     * This status is in the middle.
                     * Update the previous status to end at the start of this status.
                     */
                    await this.updateOneById({
                        id: stateBeforeThis.id,
                        data: {
                            endsAt: stateAfterThis.startsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    // Update the next status to start at the end of this status.
                    await this.updateOneById({
                        id: stateAfterThis.id,
                        data: {
                            startsAt: monitorStatusTimelineToBeDeleted.startsAt,
                        },
                        props: {
                            isRoot: true,
                        },
                    });
                    logger.debug("This status is in the middle.", {
                        monitorId: monitorId === null || monitorId === void 0 ? void 0 : monitorId.toString(),
                    });
                }
            }
            return { deleteBy, carryForward: monitorId };
        }
        return { deleteBy, carryForward: null };
    }
    async onDeleteSuccess(onDelete, _itemIdsBeforeDelete) {
        if (onDelete.carryForward) {
            // this is monitorId.
            const monitorId = onDelete.carryForward;
            // get last status of this monitor.
            const monitorStatusTimeline = await this.findOneBy({
                query: {
                    monitorId: monitorId,
                },
                sort: {
                    startsAt: SortOrder.Descending,
                },
                props: {
                    isRoot: true,
                },
                select: {
                    _id: true,
                    monitorStatusId: true,
                },
            });
            if (monitorStatusTimeline && monitorStatusTimeline.monitorStatusId) {
                await MonitorService.updateOneBy({
                    query: {
                        _id: monitorId.toString(),
                    },
                    data: {
                        currentMonitorStatusId: monitorStatusTimeline.monitorStatusId,
                    },
                    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, MonitorStatusTimeline]),
    __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=MonitorStatusTimelineService.js.map