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 DatabaseConfig from "../DatabaseConfig";
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
import CookieUtil from "../Utils/Cookie";
import JSONWebToken from "../Utils/JsonWebToken";
import logger from "../Utils/Logger";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import DatabaseService from "./DatabaseService";
import MonitorStatusService from "./MonitorStatusService";
import ProjectService from "./ProjectService";
import StatusPageDomainService from "./StatusPageDomainService";
import StatusPageLabelRuleEngineService from "./StatusPageLabelRuleEngineService";
import StatusPageOwnerRuleEngineService from "./StatusPageOwnerRuleEngineService";
import StatusPageOwnerTeamService from "./StatusPageOwnerTeamService";
import StatusPageOwnerUserService from "./StatusPageOwnerUserService";
import TeamMemberService from "./TeamMemberService";
import URL from "../../Types/API/URL";
import { Green } from "../../Types/BrandColors";
import LIMIT_MAX, { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import Typeof from "../../Types/Typeof";
import StatusPage from "../../Models/DatabaseModels/StatusPage";
import StatusPageOwnerTeam from "../../Models/DatabaseModels/StatusPageOwnerTeam";
import StatusPageOwnerUser from "../../Models/DatabaseModels/StatusPageOwnerUser";
import { AllowedStatusPageCountInFreePlan, IsBillingEnabled, } from "../EnvironmentConfig";
import { PlanType } from "../../Types/Billing/SubscriptionPlan";
import Recurring from "../../Types/Events/Recurring";
import StatusPageSubscriberService from "./StatusPageSubscriberService";
import MailService from "./MailService";
import EmailTemplateType from "../../Types/Email/EmailTemplateType";
import { StatusPageApiRoute } from "../../ServiceRoute";
import ProjectSMTPConfigService from "./ProjectSmtpConfigService";
import StatusPageResourceService from "./StatusPageResourceService";
import MonitorGroupResourceService from "./MonitorGroupResourceService";
import QueryHelper from "../Types/Database/QueryHelper";
import OneUptimeDate from "../../Types/Date";
import IncidentService from "./IncidentService";
import MonitorStatusTimelineService from "./MonitorStatusTimelineService";
import SortOrder from "../../Types/BaseDatabase/SortOrder";
import UptimeUtil from "../../Utils/Uptime/UptimeUtil";
import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
import IP from "../../Types/IP/IP";
import NotAuthenticatedException from "../../Types/Exception/NotAuthenticatedException";
import ForbiddenException from "../../Types/Exception/ForbiddenException";
import MasterPasswordRequiredException from "../../Types/Exception/MasterPasswordRequiredException";
import { MASTER_PASSWORD_COOKIE_IDENTIFIER, MASTER_PASSWORD_REQUIRED_MESSAGE, } from "../../Types/StatusPage/MasterPassword";
export class Service extends DatabaseService {
    constructor() {
        super(StatusPage);
        /*
         * Caches the resolved status page URL per statusPageId. `getStatusPageURL`
         * is called inside per-subscriber notification loops (see
         * `StatusPageSubscriberService`), where a single batch can fire N×
         * Postgres lookups against `StatusPageDomain`. SSL-provisioned custom
         * domains change rarely (provisioning takes minutes), so a 60s staleness
         * window is acceptable. Callers that need stronger consistency can call
         * `clearStatusPageUrlCache` after writes to the underlying domain.
         */
        this.statusPageUrlCache = new InMemoryTTLCache(10000);
    }
    clearStatusPageUrlCache(statusPageId) {
        if (statusPageId) {
            this.statusPageUrlCache.delete(statusPageId.toString());
            return;
        }
        this.statusPageUrlCache.clear();
    }
    static getDefaultEmailFooterText() {
        return "This is an automated email sent to you because you are subscribed to this Status Page.";
    }
    static getSubscriberEmailFooterText(statusPage) {
        if (statusPage.enableCustomSubscriberEmailNotificationFooterText &&
            statusPage.subscriberEmailNotificationFooterText) {
            return statusPage.subscriberEmailNotificationFooterText;
        }
        return this.getDefaultEmailFooterText();
    }
    async onBeforeCreate(createBy) {
        var _a, _b;
        if (!createBy.data.projectId) {
            throw new BadDataException("projectId is required");
        }
        // if the project is on the free plan, then only allow 1 status page.
        if (IsBillingEnabled) {
            const currentPlan = await ProjectService.getCurrentPlan(createBy.data.projectId);
            if (currentPlan.isSubscriptionUnpaid) {
                throw new BadDataException("Your subscription is unpaid. Please update your payment method and pay all the outstanding invoices to add more status pages.");
            }
            if (currentPlan.plan === PlanType.Free) {
                const statusPageCount = await this.countBy({
                    query: {
                        projectId: createBy.data.projectId,
                    },
                    props: {
                        isRoot: true,
                    },
                });
                if (statusPageCount.toNumber() >= AllowedStatusPageCountInFreePlan) {
                    throw new BadDataException(`You have reached the maximum allowed status page limit for the free plan. Please upgrade your plan to add more status pages.`);
                }
            }
        }
        if (!createBy.data.downtimeMonitorStatuses) {
            const monitorStatuses = await MonitorStatusService.findBy({
                query: {
                    projectId: createBy.data.projectId,
                },
                select: {
                    _id: true,
                    isOperationalState: true,
                },
                props: {
                    isRoot: true,
                },
                skip: 0,
                limit: LIMIT_PER_PROJECT,
            });
            const getNonOperationStatuses = monitorStatuses.filter((monitorStatus) => {
                return !monitorStatus.isOperationalState;
            });
            createBy.data.downtimeMonitorStatuses = getNonOperationStatuses;
        }
        if (!createBy.data.defaultBarColor) {
            createBy.data.defaultBarColor = Green;
        }
        /*
         * For new status pages, set enableCustomSubscriberEmailNotificationFooterText to false by default
         * and provide a default custom footer text only if not provided
         */
        if (createBy.data.enableCustomSubscriberEmailNotificationFooterText ===
            undefined) {
            createBy.data.enableCustomSubscriberEmailNotificationFooterText = false;
        }
        if (!createBy.data.subscriberEmailNotificationFooterText) {
            createBy.data.subscriberEmailNotificationFooterText =
                "This is an automated email sent to you because you are subscribed to " +
                    (((_a = createBy.data) === null || _a === void 0 ? void 0 : _a.pageTitle) || ((_b = createBy.data) === null || _b === void 0 ? void 0 : _b.name) || "Status Page");
        }
        return {
            createBy,
            carryForward: null,
        };
    }
    async onCreateSuccess(onCreate, createdItem) {
        // Execute owner assignment asynchronously
        if (createdItem.projectId &&
            createdItem.id &&
            onCreate.createBy.miscDataProps &&
            (onCreate.createBy.miscDataProps["ownerTeams"] ||
                onCreate.createBy.miscDataProps["ownerUsers"])) {
            // Run owner assignment in background without blocking
            this.addOwners(createdItem.projectId, createdItem.id, onCreate.createBy.miscDataProps["ownerUsers"] ||
                [], onCreate.createBy.miscDataProps["ownerTeams"] ||
                [], false, onCreate.createBy.props).catch((error) => {
                var _a, _b;
                logger.error(`Error in StatusPageService owner assignment: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    statusPageId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            });
        }
        /*
         * Apply label rules first so rule-added labels are persisted before owner
         * rules run. Owner rules re-fetch labels from the DB, so this lets owner
         * rules key on rule-added labels.
         */
        if (createdItem.projectId && createdItem.id) {
            Promise.resolve()
                .then(async () => {
                await StatusPageLabelRuleEngineService.applyRulesToStatusPage(createdItem);
            })
                .then(async () => {
                await StatusPageOwnerRuleEngineService.applyRulesToStatusPage(createdItem);
            })
                .catch((error) => {
                var _a, _b;
                logger.error(`Error applying status page rules in StatusPageService.onCreateSuccess: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    statusPageId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            });
        }
        return createdItem;
    }
    async findOwners(statusPageId) {
        if (!statusPageId) {
            throw new BadDataException("statusPageId is required");
        }
        const ownerUsers = await StatusPageOwnerUserService.findBy({
            query: {
                statusPageId: statusPageId,
            },
            select: {
                _id: true,
                user: {
                    _id: true,
                    email: true,
                    name: true,
                },
            },
            props: {
                isRoot: true,
            },
            limit: LIMIT_PER_PROJECT,
            skip: 0,
        });
        const ownerTeams = await StatusPageOwnerTeamService.findBy({
            query: {
                statusPageId: statusPageId,
            },
            select: {
                _id: true,
                teamId: true,
            },
            skip: 0,
            limit: LIMIT_PER_PROJECT,
            props: {
                isRoot: true,
            },
        });
        const users = ownerUsers.map((ownerUser) => {
            return ownerUser.user;
        }) || [];
        if (ownerTeams.length > 0) {
            const teamIds = ownerTeams.map((ownerTeam) => {
                return ownerTeam.teamId;
            }) || [];
            const teamUsers = await TeamMemberService.getUsersInTeams(teamIds);
            for (const teamUser of teamUsers) {
                //check if the user is already added.
                const isUserAlreadyAdded = users.find((user) => {
                    return user.id.toString() === teamUser.id.toString();
                });
                if (!isUserAlreadyAdded) {
                    users.push(teamUser);
                }
            }
        }
        return users;
    }
    async addOwners(projectId, statusPageId, userIds, teamIds, notifyOwners, props) {
        for (let teamId of teamIds) {
            if (typeof teamId === Typeof.String) {
                teamId = new ObjectID(teamId.toString());
            }
            const teamOwner = new StatusPageOwnerTeam();
            teamOwner.statusPageId = statusPageId;
            teamOwner.projectId = projectId;
            teamOwner.teamId = teamId;
            teamOwner.isOwnerNotified = !notifyOwners;
            await StatusPageOwnerTeamService.create({
                data: teamOwner,
                props: props,
            });
        }
        for (let userId of userIds) {
            if (typeof userId === Typeof.String) {
                userId = new ObjectID(userId.toString());
            }
            const teamOwner = new StatusPageOwnerUser();
            teamOwner.statusPageId = statusPageId;
            teamOwner.projectId = projectId;
            teamOwner.userId = userId;
            teamOwner.isOwnerNotified = !notifyOwners;
            await StatusPageOwnerUserService.create({
                data: teamOwner,
                props: props,
            });
        }
    }
    async getStatusPageLinkInDashboard(projectId, statusPageId) {
        if (!projectId) {
            throw new BadDataException("projectId is required to build status page dashboard link");
        }
        if (!statusPageId) {
            throw new BadDataException("statusPageId is required to build status page dashboard link");
        }
        const dashboardUrl = await DatabaseConfig.getDashboardUrl();
        // Defensive: ensure objects have toString
        const projectIdStr = projectId.toString();
        const statusPageIdStr = statusPageId.toString();
        return URL.fromString(dashboardUrl.toString()).addRoute(`/${projectIdStr}/status-pages/${statusPageIdStr}`);
    }
    async hasReadAccess(data) {
        var _a, _b, _c, _d;
        const statusPageId = data.statusPageId;
        const req = data.req;
        try {
            // get status page by id.
            const statusPage = await this.findOneById({
                id: statusPageId,
                props: {
                    isRoot: true,
                },
                select: {
                    _id: true,
                    isPublicStatusPage: true,
                    ipWhitelist: true,
                    enableMasterPassword: true,
                    masterPassword: true,
                },
            });
            if ((statusPage === null || statusPage === void 0 ? void 0 : statusPage.ipWhitelist) && statusPage.ipWhitelist.length > 0) {
                const ipWhitelist = (_a = statusPage.ipWhitelist) === null || _a === void 0 ? void 0 : _a.split("\n");
                const ipAccessedFrom = ((_b = req.headers["x-forwarded-for"]) === null || _b === void 0 ? void 0 : _b.toString()) ||
                    ((_c = req.headers["x-real-ip"]) === null || _c === void 0 ? void 0 : _c.toString()) ||
                    req.socket.remoteAddress ||
                    req.ip ||
                    req.ips[0];
                if (!ipAccessedFrom) {
                    logger.error("IP address not found in request.", {
                        statusPageId: statusPageId === null || statusPageId === void 0 ? void 0 : statusPageId.toString(),
                    });
                    return {
                        hasReadAccess: false,
                        error: new ForbiddenException("Unable to verify IP address for status page access."),
                    };
                }
                const isIPWhitelisted = IP.isInWhitelist({
                    ips: (ipAccessedFrom === null || ipAccessedFrom === void 0 ? void 0 : ipAccessedFrom.split(",").map((i) => {
                        return i.trim();
                    })) || [],
                    whitelist: ipWhitelist,
                });
                if (!isIPWhitelisted) {
                    logger.error(`IP address ${ipAccessedFrom} is not whitelisted for status page ${statusPageId.toString()}.`, { statusPageId: statusPageId === null || statusPageId === void 0 ? void 0 : statusPageId.toString() });
                    return {
                        hasReadAccess: false,
                        error: new ForbiddenException(`Your IP address ${ipAccessedFrom} is blocked from accessing this status page.`),
                    };
                }
            }
            if (statusPage && statusPage.isPublicStatusPage) {
                return {
                    hasReadAccess: true,
                };
            }
            // token decode.
            const token = CookieUtil.getCookieFromExpressRequest(req, CookieUtil.getUserTokenKey(statusPageId));
            if (token) {
                try {
                    const decoded = JSONWebToken.decode(token);
                    if (((_d = decoded.statusPageId) === null || _d === void 0 ? void 0 : _d.toString()) === statusPageId.toString()) {
                        return {
                            hasReadAccess: true,
                        };
                    }
                }
                catch (err) {
                    logger.error(err, {
                        statusPageId: statusPageId === null || statusPageId === void 0 ? void 0 : statusPageId.toString(),
                    });
                }
            }
            const shouldEnforceMasterPassword = Boolean(statusPage &&
                statusPage.enableMasterPassword &&
                statusPage.masterPassword &&
                !statusPage.isPublicStatusPage);
            if (shouldEnforceMasterPassword) {
                const hasValidMasterPassword = this.hasValidMasterPasswordCookie({
                    req,
                    statusPageId,
                });
                if (hasValidMasterPassword) {
                    return {
                        hasReadAccess: true,
                    };
                }
                return {
                    hasReadAccess: false,
                    error: new MasterPasswordRequiredException(MASTER_PASSWORD_REQUIRED_MESSAGE),
                };
            }
        }
        catch (err) {
            logger.error(err, {
                statusPageId: statusPageId === null || statusPageId === void 0 ? void 0 : statusPageId.toString(),
            });
        }
        return {
            hasReadAccess: false,
            error: new NotAuthenticatedException("You do not have access to this status page. Please login to view the status page."),
        };
    }
    hasValidMasterPasswordCookie(data) {
        var _a;
        const token = CookieUtil.getCookieFromExpressRequest(data.req, CookieUtil.getStatusPageMasterPasswordKey(data.statusPageId));
        if (!token) {
            return false;
        }
        try {
            const payload = JSONWebToken.decodeJsonPayload(token);
            return (payload["statusPageId"] === data.statusPageId.toString() &&
                payload["type"] === MASTER_PASSWORD_COOKIE_IDENTIFIER);
        }
        catch (err) {
            logger.error(err, {
                statusPageId: (_a = data.statusPageId) === null || _a === void 0 ? void 0 : _a.toString(),
            });
        }
        return false;
    }
    async getMonitorStatusTimelineForStatusPage(data) {
        const startDate = data.startDate;
        const endDate = data.endDate;
        let monitorStatusTimelines = [];
        if (data.monitorIds.length > 0) {
            monitorStatusTimelines = await MonitorStatusTimelineService.findBy({
                query: {
                    monitorId: QueryHelper.any(data.monitorIds),
                    endsAt: QueryHelper.inBetween(startDate, endDate),
                },
                select: {
                    monitorId: true,
                    createdAt: true,
                    endsAt: true,
                    startsAt: true,
                    monitorStatus: {
                        name: true,
                        color: true,
                        priority: true,
                    },
                },
                sort: {
                    createdAt: SortOrder.Descending,
                },
                skip: 0,
                limit: LIMIT_MAX, // This can be optimized.
                props: {
                    isRoot: true,
                },
            });
            monitorStatusTimelines = monitorStatusTimelines.concat(await MonitorStatusTimelineService.findBy({
                query: {
                    monitorId: QueryHelper.any(data.monitorIds),
                    endsAt: QueryHelper.isNull(),
                },
                select: {
                    monitorId: true,
                    createdAt: true,
                    endsAt: true,
                    startsAt: true,
                    monitorStatus: {
                        name: true,
                        color: true,
                        priority: true,
                    },
                },
                sort: {
                    createdAt: SortOrder.Descending,
                },
                skip: 0,
                limit: LIMIT_MAX, // This can be optimized.
                props: {
                    isRoot: true,
                },
            }));
            // sort monitorStatusTimelines by createdAt.
            monitorStatusTimelines = monitorStatusTimelines.sort((a, b) => {
                if (!a.createdAt || !b.createdAt) {
                    return 0;
                }
                return b.createdAt.getTime() - a.createdAt.getTime();
            });
        }
        return monitorStatusTimelines;
    }
    async getStatusPageURL(statusPageId) {
        const cacheKey = statusPageId.toString();
        const cached = this.statusPageUrlCache.get(cacheKey);
        if (cached !== undefined) {
            return cached;
        }
        const domain = await StatusPageDomainService.findOneBy({
            query: {
                statusPageId: statusPageId,
                isSslProvisioned: true,
            },
            select: {
                fullDomain: true,
            },
            props: {
                isRoot: true,
                ignoreHooks: true,
            },
        });
        let statusPageURL = (domain === null || domain === void 0 ? void 0 : domain.fullDomain)
            ? `https://${domain.fullDomain}`
            : "";
        if (!statusPageURL) {
            const host = await DatabaseConfig.getHost();
            const httpProtocol = await DatabaseConfig.getHttpProtocol();
            // 'https://local.oneuptime.com/status-page/40092fb5-cc33-4995-b532-b4e49c441c98'
            statusPageURL = new URL(httpProtocol, host)
                .addRoute("/status-page/" + statusPageId.toString())
                .toString();
        }
        this.statusPageUrlCache.set(cacheKey, statusPageURL, 60000);
        return statusPageURL;
    }
    async getStatusPageFirstURL(statusPageId) {
        var _a;
        const domains = await StatusPageDomainService.findBy({
            query: {
                statusPageId: statusPageId,
                isSslProvisioned: true,
            },
            select: {
                fullDomain: true,
            },
            skip: 0,
            limit: LIMIT_PER_PROJECT,
            props: {
                isRoot: true,
                ignoreHooks: true,
            },
        });
        let statusPageURL = "";
        if (domains.length === 0) {
            const host = await DatabaseConfig.getHost();
            const httpProtocol = await DatabaseConfig.getHttpProtocol();
            // 'https://local.oneuptime.com/status-page/40092fb5-cc33-4995-b532-b4e49c441c98'
            statusPageURL = new URL(httpProtocol, host)
                .addRoute("/status-page/" + statusPageId.toString())
                .toString();
        }
        else {
            statusPageURL = ((_a = domains[0]) === null || _a === void 0 ? void 0 : _a.fullDomain) || "";
        }
        return statusPageURL;
    }
    async onBeforeUpdate(updateBy) {
        // is enabling SMS subscribers.
        if (updateBy.data.enableSmsSubscribers) {
            const statusPagesToBeUpdated = await this.findBy({
                query: updateBy.query,
                select: {
                    _id: true,
                    projectId: true,
                },
                props: {
                    isRoot: true,
                },
                skip: 0,
                limit: LIMIT_PER_PROJECT,
            });
            for (const statusPage of statusPagesToBeUpdated) {
                const isSMSEnabled = await ProjectService.isSMSNotificationsEnabled(statusPage.projectId);
                if (!isSMSEnabled) {
                    throw new BadDataException("SMS notifications are not enabled for this project. Please enable SMS notifications in the Project Settings > Notifications Settings.");
                }
            }
        }
        if (updateBy.data.reportStartDateTime ||
            updateBy.data.reportRecurringInterval ||
            updateBy.data.sendNextReportBy) {
            const statusPages = await this.findBy({
                query: updateBy.query,
                select: {
                    _id: true,
                    reportStartDateTime: true,
                    reportRecurringInterval: true,
                },
                props: {
                    isRoot: true,
                },
                skip: 0,
                limit: LIMIT_PER_PROJECT,
            });
            for (const statusPage of statusPages) {
                const rerportStartDate = updateBy.data.reportStartDateTime ||
                    statusPage.reportStartDateTime;
                const reportRecurringInterval = Recurring.fromJSON(updateBy.data.reportRecurringInterval ||
                    statusPage.reportRecurringInterval);
                if (rerportStartDate && reportRecurringInterval) {
                    const nextReportDate = Recurring.getNextDate(rerportStartDate, reportRecurringInterval);
                    updateBy.data.sendNextReportBy = nextReportDate;
                }
            }
        }
        return {
            carryForward: null,
            updateBy: updateBy,
        };
    }
    async sendEmailReport(data) {
        var _a, _b, _c, _d;
        const host = await DatabaseConfig.getHost();
        const httpProtocol = await DatabaseConfig.getHttpProtocol();
        const statusPages = await StatusPageSubscriberService.getStatusPagesToSendNotification([
            data.statusPageId,
        ]);
        if (statusPages.length === 0) {
            throw new BadDataException("Status page not found");
        }
        const statuspage = statusPages[0];
        if (!statuspage.id) {
            throw new BadDataException("Status page not found");
        }
        const statusPageURL = await this.getStatusPageURL(statuspage.id);
        const statusPageName = statuspage.pageTitle || statuspage.name || "Status Page";
        const statusPageIdString = ((_a = statuspage.id) === null || _a === void 0 ? void 0 : _a.toString()) || ((_b = statuspage._id) === null || _b === void 0 ? void 0 : _b.toString()) || null;
        const report = await this.getReportByStatusPage({
            statusPageId: statuspage.id,
            historyDays: statuspage.reportDataInDays || 14,
        });
        const sendEmail = async (email, unsubscribeUrl) => {
            // send email here.
            MailService.sendMail({
                toEmail: email,
                templateType: EmailTemplateType.StatusPageSubscriberReport,
                vars: {
                    statusPageName: statusPageName,
                    subscriberEmailNotificationFooterText: Service.getSubscriberEmailFooterText(statuspage),
                    statusPageUrl: statusPageURL,
                    detailsUrl: statusPageURL,
                    hasResources: report.totalResources > 0 ? "true" : "false",
                    report: report,
                    logoUrl: statuspage.logoFileId && statusPageIdString
                        ? new URL(httpProtocol, host)
                            .addRoute(StatusPageApiRoute)
                            .addRoute(`/logo/${statusPageIdString}`)
                            .toString()
                        : "",
                    isPublicStatusPage: statuspage.isPublicStatusPage
                        ? "true"
                        : "false",
                    unsubscribeUrl: (unsubscribeUrl === null || unsubscribeUrl === void 0 ? void 0 : unsubscribeUrl.toString()) || "",
                },
                subject: "[Report] " + statusPageName,
            }, {
                mailServer: ProjectSMTPConfigService.toEmailServer(statuspage.smtpConfig),
                projectId: statuspage.projectId,
                statusPageId: statuspage.id,
            }).catch((err) => {
                var _a, _b;
                logger.error(err, {
                    projectId: (_a = statuspage.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    statusPageId: (_b = statuspage.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            });
        };
        if (data.email) {
            // force send to this email instead of sending to all subscribers.
            await sendEmail(data.email, null);
            return; // don't notify subscribers when explicitly sending a test email.
        }
        const subscribers = await StatusPageSubscriberService.getSubscribersByStatusPage(statuspage.id, {
            isRoot: true,
            ignoreHooks: true,
        });
        for (const subscriber of subscribers) {
            try {
                if (!subscriber._id) {
                    continue;
                }
                const shouldNotifySubscriber = !subscriber.isUnsubscribed;
                if (!shouldNotifySubscriber) {
                    continue;
                }
                const unsubscribeUrl = StatusPageSubscriberService.getUnsubscribeLink(URL.fromString(statusPageURL), subscriber.id).toString();
                if (subscriber.subscriberEmail) {
                    await sendEmail(subscriber.subscriberEmail, URL.fromString(unsubscribeUrl));
                }
                if (subscriber.subscriberPhone) {
                    continue; // Cant send Status Page reports to SMS subscribers.
                }
            }
            catch (err) {
                logger.error(err, {
                    projectId: (_c = statuspage.projectId) === null || _c === void 0 ? void 0 : _c.toString(),
                    statusPageId: (_d = statuspage.id) === null || _d === void 0 ? void 0 : _d.toString(),
                });
            }
        }
    }
    async getReportByStatusPage(data) {
        const statusPage = await this.findOneById({
            id: data.statusPageId,
            props: {
                isRoot: true,
            },
            select: {
                downtimeMonitorStatuses: true,
            },
        });
        if (!statusPage) {
            throw new BadDataException("Status page not found");
        }
        const statusPageResources = await this.getStatusPageResources({
            statusPageId: data.statusPageId,
        });
        const numberOfDays = data.historyDays || 14;
        const endDate = OneUptimeDate.getCurrentDate();
        const startDate = OneUptimeDate.getSomeDaysAgo(numberOfDays);
        const startAndEndDate = `${numberOfDays} days (${OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(startDate, true)} - ${OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(endDate, true)})`;
        if (statusPageResources.length === 0) {
            return {
                reportDates: startAndEndDate,
                totalResources: 0,
                totalIncidents: 0,
                averageUptimePercent: "0%",
                totalDowntimeInHoursAndMinutes: "0",
                resources: [],
            };
        }
        const incidentCount = await this.getIncidentCountOnStatusPage({
            statusPageId: data.statusPageId,
            historyDays: data.historyDays,
        });
        const monitors = await this.getMonitorIdsOnStatusPage({
            statusPageId: data.statusPageId,
        });
        const timeline = await this.getMonitorStatusTimelineForStatusPage({
            monitorIds: monitors.monitorsOnStatusPage,
            startDate: startDate,
            endDate: endDate,
        });
        const reportItems = [];
        for (const resource of statusPageResources) {
            // for each of these resource, calculate uptime percent.
            let monitorIdsForThisResource = [];
            if (resource.monitorId) {
                monitorIdsForThisResource.push(resource.monitorId);
            }
            if (resource.monitorGroupId) {
                const groupId = resource.monitorGroupId.toString();
                monitorIdsForThisResource = monitorIdsForThisResource.concat(monitors.monitorsInGroup[groupId] || []);
            }
            const timelineForThisResource = timeline.filter((item) => {
                return monitorIdsForThisResource.find((id) => {
                    var _a;
                    return id.toString() === ((_a = item.monitorId) === null || _a === void 0 ? void 0 : _a.toString());
                });
            });
            const uptimePercent = UptimeUtil.calculateUptimePercentage(timelineForThisResource, resource.uptimePercentPrecision || UptimePrecision.TWO_DECIMAL, statusPage.downtimeMonitorStatuses);
            const downtime = UptimeUtil.getTotalDowntimeInSeconds(timelineForThisResource, statusPage.downtimeMonitorStatuses);
            const reportItem = {
                resourceName: resource.displayName || "",
                totalIncidentCount: await this.getIncidentCountByMonitorIds({
                    monitorIds: monitorIdsForThisResource,
                    historyDays: data.historyDays,
                }),
                uptimePercent: uptimePercent,
                uptimePercentAsString: `${uptimePercent}%`,
                downtimeInHoursAndMinutes: OneUptimeDate.convertMinutesToDaysHoursAndMinutes(Math.ceil(downtime.totalDowntimeInSeconds / 60)),
            };
            reportItems.push(reportItem);
        }
        const avgUptimePercent = reportItems.reduce((acc, item) => {
            return acc + item.uptimePercent;
        }, 0) / reportItems.length;
        const avgUptimePercentString = avgUptimePercent.toFixed(2) + "%";
        const totalDowntimeInSeconds = UptimeUtil.getTotalDowntimeInSeconds(timeline, statusPage.downtimeMonitorStatuses);
        return {
            reportDates: startAndEndDate,
            totalResources: statusPageResources.length,
            totalIncidents: incidentCount,
            averageUptimePercent: avgUptimePercentString,
            resources: reportItems,
            totalDowntimeInHoursAndMinutes: OneUptimeDate.convertMinutesToDaysHoursAndMinutes(Math.ceil(totalDowntimeInSeconds.totalDowntimeInSeconds / 60)),
        };
    }
    async getIncidentCountByMonitorIds(data) {
        const today = OneUptimeDate.getCurrentDate();
        const historyDays = OneUptimeDate.getSomeDaysAgo(data.historyDays || 14);
        const incidentCount = await IncidentService.countBy({
            query: {
                monitors: data.monitorIds,
                createdAt: QueryHelper.inBetween(historyDays, today),
            },
            props: {
                isRoot: true,
            },
        });
        return incidentCount.toNumber();
    }
    async getIncidentCountOnStatusPage(data) {
        const monitorsOnStatusPage = await this.getMonitorIdsOnStatusPage({
            statusPageId: data.statusPageId,
        });
        return this.getIncidentCountByMonitorIds({
            monitorIds: monitorsOnStatusPage.monitorsOnStatusPage,
            historyDays: data.historyDays,
        });
    }
    async getMonitorIdsOnStatusPage(data) {
        const statusPageResources = await this.getStatusPageResources(data);
        const monitorGroupIds = statusPageResources
            .map((resource) => {
            return resource.monitorGroupId;
        })
            .filter((id) => {
            return Boolean(id); // remove nulls
        });
        const monitorsInGroup = {};
        // get monitor status charts.
        const monitorsOnStatusPage = statusPageResources
            .map((monitor) => {
            return monitor.monitorId;
        })
            .filter((id) => {
            return Boolean(id); // remove nulls
        });
        for (const monitorGroupId of monitorGroupIds) {
            // get current status of monitors in the group.
            // get monitors in the group.
            const groupResources = await MonitorGroupResourceService.findBy({
                query: {
                    monitorGroupId: monitorGroupId,
                },
                select: {
                    monitorId: true,
                },
                props: {
                    isRoot: true,
                },
                limit: LIMIT_PER_PROJECT,
                skip: 0,
            });
            const monitorsInGroupIds = groupResources
                .map((resource) => {
                return resource.monitorId;
            })
                .filter((id) => {
                return Boolean(id); // remove nulls
            });
            for (const monitorId of monitorsInGroupIds) {
                if (!monitorsOnStatusPage.find((item) => {
                    return item.toString() === monitorId.toString();
                })) {
                    monitorsOnStatusPage.push(monitorId);
                }
            }
            monitorsInGroup[monitorGroupId.toString()] = monitorsInGroupIds;
        }
        return {
            monitorsOnStatusPage: monitorsOnStatusPage,
            monitorsInGroup: monitorsInGroup,
        };
    }
    async getStatusPageResources(data) {
        // get monitors on status page.
        const statusPageResources = await StatusPageResourceService.findBy({
            query: {
                statusPageId: data.statusPageId,
            },
            select: {
                statusPageGroupId: true,
                statusPageGroup: {
                    name: true,
                    viewMode: true,
                    rowAxisLabel: true,
                    columnAxisLabel: true,
                },
                monitorId: true,
                displayTooltip: true,
                displayDescription: true,
                displayName: true,
                rowAxisValue: true,
                columnAxisValue: true,
                monitor: {
                    _id: true,
                    currentMonitorStatusId: true,
                },
                monitorGroupId: true,
                order: true,
            },
            skip: 0,
            limit: LIMIT_PER_PROJECT,
            props: {
                isRoot: true,
            },
        });
        // sort by order and then return
        return statusPageResources.sort((a, b) => {
            return a.order - b.order;
        });
    }
    async getMonitorGroupCurrentStatuses(data) {
        const monitorGroupCurrentStatuses = {};
        for (const resource of data.statusPageResources) {
            if (resource.monitorGroupId) {
                const monitorGroupResources = await MonitorGroupResourceService.findBy({
                    query: {
                        monitorGroupId: resource.monitorGroupId,
                    },
                    select: {
                        monitorId: true,
                        monitor: {
                            currentMonitorStatusId: true,
                        },
                    },
                    skip: 0,
                    limit: LIMIT_PER_PROJECT,
                    props: {
                        isRoot: true,
                    },
                });
                const statuses = monitorGroupResources
                    .filter((item) => {
                    return (item.monitor &&
                        item.monitor.currentMonitorStatusId &&
                        item.monitorId);
                })
                    .map((item) => {
                    return item.monitor.currentMonitorStatusId;
                });
                let worstStatus = null;
                for (const statusId of statuses) {
                    const status = data.monitorStatuses.find((status) => {
                        var _a;
                        return ((_a = status._id) === null || _a === void 0 ? void 0 : _a.toString()) === statusId.toString();
                    });
                    if (status &&
                        (!worstStatus || status.priority < worstStatus.priority)) {
                        worstStatus = status;
                    }
                }
                if (worstStatus && worstStatus._id) {
                    monitorGroupCurrentStatuses[resource.monitorGroupId.toString()] =
                        new ObjectID(worstStatus._id);
                }
            }
        }
        return monitorGroupCurrentStatuses;
    }
    getOverallMonitorStatus(data) {
        var _a, _b, _c, _d;
        let currentStatus = data.monitorStatuses.length > 0 && data.monitorStatuses[0]
            ? data.monitorStatuses[0]
            : null;
        const dict = {};
        for (const resource of data.statusPageResources) {
            if ((_a = resource.monitor) === null || _a === void 0 ? void 0 : _a.currentMonitorStatusId) {
                if (!Object.keys(dict).includes(((_b = resource.monitor) === null || _b === void 0 ? void 0 : _b.currentMonitorStatusId.toString()) || "")) {
                    dict[(_d = (_c = resource.monitor) === null || _c === void 0 ? void 0 : _c.currentMonitorStatusId) === null || _d === void 0 ? void 0 : _d.toString()] = 1;
                }
                else {
                    dict[resource.monitor.currentMonitorStatusId.toString()]++;
                }
            }
        }
        // check status of monitor groups.
        for (const groupId in data.monitorGroupCurrentStatuses) {
            const statusId = data.monitorGroupCurrentStatuses[groupId];
            if (statusId) {
                if (!Object.keys(dict).includes(statusId.toString() || "")) {
                    dict[statusId.toString()] = 1;
                }
                else {
                    dict[statusId.toString()]++;
                }
            }
        }
        for (const monitorStatus of data.monitorStatuses) {
            if (monitorStatus._id && dict[monitorStatus._id]) {
                currentStatus = monitorStatus;
            }
        }
        return currentStatus;
    }
}
__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, StatusPage]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onCreateSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "findOwners", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID,
        Array,
        Array, Boolean, Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "addOwners", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID,
        ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getStatusPageLinkInDashboard", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "hasReadAccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getMonitorStatusTimelineForStatusPage", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getStatusPageURL", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getStatusPageFirstURL", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onBeforeUpdate", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "sendEmailReport", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getReportByStatusPage", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getIncidentCountByMonitorIds", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getIncidentCountOnStatusPage", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getMonitorIdsOnStatusPage", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getStatusPageResources", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getMonitorGroupCurrentStatuses", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Object)
], Service.prototype, "getOverallMonitorStatus", null);
export default new Service();
//# sourceMappingURL=StatusPageService.js.map