import UserMiddleware from "../Middleware/UserAuthorization";
import Express from "../Utils/Express";
import Response from "../Utils/Response";
import BadDataException from "../../Types/Exception/BadDataException";
import CommonAPI from "./CommonAPI";
import TelemetryType from "../../Types/Telemetry/TelemetryType";
import TelemetryAttributeService from "../Services/TelemetryAttributeService";
import LogAggregationService from "../Services/LogAggregationService";
import TraceAggregationService from "../Services/TraceAggregationService";
import ExceptionAggregationService from "../Services/ExceptionAggregationService";
import MetricAggregationService from "../Services/MetricAggregationService";
import ProfileAggregationService from "../Services/ProfileAggregationService";
import PprofEncoder from "../Utils/Profile/PprofEncoder";
import ProfileService from "../Services/ProfileService";
import ProfileSampleService from "../Services/ProfileSampleService";
import SortOrder from "../../Types/BaseDatabase/SortOrder";
import Permission from "../../Types/Permission";
import ObjectID from "../../Types/ObjectID";
import OneUptimeDate from "../../Types/Date";
import ResourceFacetResolver from "../Utils/Telemetry/ResourceFacetResolver";
const router = Express.getRouter();
const createTelemetryReadAccessGuard = (signalReadPermission) => {
    return [
        UserMiddleware.getUserMiddleware,
        UserMiddleware.requireUserAuthentication,
        UserMiddleware.requirePermission({
            permissions: [
                Permission.ProjectOwner,
                Permission.ProjectAdmin,
                Permission.ProjectMember,
                Permission.Viewer,
                Permission.TelemetryAdmin,
                Permission.TelemetryMember,
                Permission.TelemetryViewer,
                signalReadPermission,
            ],
        }),
    ];
};
// Mirrors the read access control declared on the Log analytics model.
const requireLogReadAccess = createTelemetryReadAccessGuard(Permission.ReadTelemetryServiceLog);
// Mirrors the read access control declared on the Span analytics model.
const requireTraceReadAccess = createTelemetryReadAccessGuard(Permission.ReadTelemetryServiceTraces);
/*
 * Mirrors the read access control declared on the Metric analytics model,
 * whose table-level read list grants ReadTelemetryServiceTraces rather than
 * ReadTelemetryServiceMetrics. The guard follows the model declaration so
 * these routes stay in lockstep with the model-backed CRUD API; if the model
 * ever switches to ReadTelemetryServiceMetrics this must change with it.
 */
const requireMetricReadAccess = createTelemetryReadAccessGuard(Permission.ReadTelemetryServiceTraces);
/*
 * Mirrors the read access control declared on the ExceptionInstance
 * analytics model.
 */
const requireExceptionReadAccess = createTelemetryReadAccessGuard(Permission.ReadTelemetryException);
/*
 * Mirrors the read access control declared on the Profile / ProfileSample
 * analytics models.
 */
const requireProfileReadAccess = createTelemetryReadAccessGuard(Permission.ReadTelemetryServiceProfiles);
router.post("/telemetry/metrics/get-attributes", ...requireMetricReadAccess, async (req, res, next) => {
    return getAttributes(req, res, next, TelemetryType.Metric);
});
router.post("/telemetry/metrics/get-attribute-values", ...requireMetricReadAccess, async (req, res, next) => {
    return getAttributeValues(req, res, next, TelemetryType.Metric);
});
router.post("/telemetry/logs/get-attributes", ...requireLogReadAccess, async (req, res, next) => {
    return getAttributes(req, res, next, TelemetryType.Log);
});
router.post("/telemetry/logs/get-attribute-values", ...requireLogReadAccess, async (req, res, next) => {
    return getAttributeValues(req, res, next, TelemetryType.Log);
});
router.post("/telemetry/traces/get-attributes", ...requireTraceReadAccess, async (req, res, next) => {
    return getAttributes(req, res, next, TelemetryType.Trace);
});
router.post("/telemetry/traces/get-attribute-values", ...requireTraceReadAccess, async (req, res, next) => {
    return getAttributeValues(req, res, next, TelemetryType.Trace);
});
router.post("/telemetry/exceptions/get-attributes", ...requireExceptionReadAccess, async (req, res, next) => {
    return getAttributes(req, res, next, TelemetryType.Exception);
});
router.post("/telemetry/exceptions/get-attribute-values", ...requireExceptionReadAccess, async (req, res, next) => {
    return getAttributeValues(req, res, next, TelemetryType.Exception);
});
const getAttributes = async (req, res, next, telemetryType) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!databaseProps) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid User Sesssion"));
        }
        if (!databaseProps.tenantId) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const metricName = req.body["metricName"] && typeof req.body["metricName"] === "string"
            ? req.body["metricName"]
            : undefined;
        const attributes = await TelemetryAttributeService.fetchAttributes({
            projectId: databaseProps.tenantId,
            telemetryType,
            metricName,
        });
        return Response.sendJsonObjectResponse(req, res, {
            attributes: attributes,
        });
    }
    catch (err) {
        next(err);
    }
};
const getAttributeValues = async (req, res, next, telemetryType) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!databaseProps) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid User Session"));
        }
        if (!databaseProps.tenantId) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const attributeKey = req.body["attributeKey"] && typeof req.body["attributeKey"] === "string"
            ? req.body["attributeKey"]
            : undefined;
        if (!attributeKey) {
            return Response.sendErrorResponse(req, res, new BadDataException("attributeKey is required"));
        }
        const metricName = req.body["metricName"] && typeof req.body["metricName"] === "string"
            ? req.body["metricName"]
            : undefined;
        const searchText = req.body["searchText"] && typeof req.body["searchText"] === "string"
            ? req.body["searchText"]
            : undefined;
        const values = await TelemetryAttributeService.fetchAttributeValues({
            projectId: databaseProps.tenantId,
            telemetryType,
            metricName,
            attributeKey,
            searchText,
        });
        return Response.sendJsonObjectResponse(req, res, {
            values: values,
        });
    }
    catch (err) {
        next(err);
    }
};
// --- Log Histogram Endpoint ---
router.post("/telemetry/logs/histogram", ...requireLogReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const bucketSizeInMinutes = body["bucketSizeInMinutes"] ||
            computeDefaultBucketSize(startTime, endTime);
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const entityKeys = body["entityKeys"]
            ? body["entityKeys"]
            : undefined;
        const severityTexts = body["severityTexts"]
            ? body["severityTexts"]
            : undefined;
        const bodySearchText = body["bodySearchText"]
            ? body["bodySearchText"]
            : undefined;
        const traceIds = body["traceIds"]
            ? body["traceIds"]
            : undefined;
        const spanIds = body["spanIds"]
            ? body["spanIds"]
            : undefined;
        const attributes = body["attributes"]
            ? body["attributes"]
            : undefined;
        const request = {
            projectId: databaseProps.tenantId,
            startTime,
            endTime,
            bucketSizeInMinutes,
            serviceIds,
            entityKeys,
            severityTexts,
            bodySearchText,
            traceIds,
            spanIds,
            attributes,
        };
        const buckets = await LogAggregationService.getHistogram(request);
        return Response.sendJsonObjectResponse(req, res, {
            buckets: buckets,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Log Facets Endpoint ---
router.post("/telemetry/logs/facets", ...requireLogReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const facetKeys = body["facetKeys"]
            ? body["facetKeys"]
            : ["severityText", "primaryEntityId"];
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const limit = body["limit"] || 500;
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const entityKeys = body["entityKeys"]
            ? body["entityKeys"]
            : undefined;
        const severityTexts = body["severityTexts"]
            ? body["severityTexts"]
            : undefined;
        const bodySearchText = body["bodySearchText"]
            ? body["bodySearchText"]
            : undefined;
        const traceIds = body["traceIds"]
            ? body["traceIds"]
            : undefined;
        const spanIds = body["spanIds"]
            ? body["spanIds"]
            : undefined;
        const attributes = body["attributes"]
            ? body["attributes"]
            : undefined;
        /*
         * Per-facet partial-match filter applied at the Postgres source-of-truth
         * lookup stage. Only consulted for resource facets (primaryEntityId /
         * hostId / dockerHostId / kubernetesClusterId) — other facets continue
         * to filter client-side over the loaded value list.
         */
        const facetSearchText = body["facetSearchText"]
            ? body["facetSearchText"]
            : undefined;
        /*
         * Capture tenantId locally so TypeScript narrowing survives the
         * async closure below (narrowing is lost across closure boundaries).
         */
        const projectId = databaseProps.tenantId;
        /*
         * Run facet queries in parallel so a slow individual facet can't
         * starve the endpoint. Per-facet errors degrade gracefully to [].
         */
        const facetResults = await Promise.all(facetKeys.map(async (facetKey) => {
            try {
                const request = {
                    projectId,
                    startTime,
                    endTime,
                    facetKey,
                    limit,
                    serviceIds,
                    entityKeys,
                    severityTexts,
                    bodySearchText,
                    traceIds,
                    spanIds,
                    attributes,
                };
                const values = await LogAggregationService.getFacetValues(request);
                return [facetKey, values];
            }
            catch (_a) {
                return [facetKey, []];
            }
        }));
        const facets = Object.fromEntries(facetResults);
        /*
         * Replace resource-facet results with the Postgres source-of-truth list
         * (filtered by facetSearchText and enriched with displayName). See the
         * trace facets handler above for the rationale — same pattern, same
         * benefit: low-volume resources stay visible and search can reach
         * resources outside the ClickHouse sample window.
         */
        const resourceSpecs = facetKeys
            .filter((key) => {
            return ResourceFacetResolver.isResourceFacet(key);
        })
            .map((key) => {
            const counts = new Map();
            for (const fv of facets[key] || []) {
                counts.set(fv.value, fv.count);
            }
            return {
                facetKey: key,
                counts,
                searchText: facetSearchText === null || facetSearchText === void 0 ? void 0 : facetSearchText[key],
                limit,
            };
        });
        if (resourceSpecs.length > 0) {
            const resolved = await ResourceFacetResolver.resolve(projectId, resourceSpecs);
            for (const key of Object.keys(resolved)) {
                facets[key] = resolved[key];
            }
        }
        return Response.sendJsonObjectResponse(req, res, {
            facets: facets,
        });
    }
    catch (err) {
        next(err);
    }
});
/*
 * Shared body parsing for every trace aggregation endpoint (histogram,
 * facets, analytics). Defensive about shapes: arrays are validated and
 * filtered to strings, booleans/numbers use strict typeof checks (JSON null
 * or a stringly-typed value must mean "no filter", never an active
 * predicate).
 */
function parseTraceFilterBody(body) {
    var _a;
    const serviceIds = Array.isArray(body["serviceIds"])
        ? body["serviceIds"]
            .filter((v) => {
            return typeof v === "string";
        })
            .map((id) => {
            return new ObjectID(id);
        })
        : undefined;
    const stringArray = (key) => {
        return Array.isArray(body[key])
            ? body[key].filter((v) => {
                return typeof v === "string";
            })
            : undefined;
    };
    /*
     * Numeric strings are coerced (stringly-typed clients worked before the
     * parsing was centralized) — dropping them would silently widen the
     * filter to all statuses.
     */
    const statusCodes = Array.isArray(body["statusCodes"])
        ? body["statusCodes"]
            .map((v) => {
            return typeof v === "number" ? v : Number(v);
        })
            .filter((v) => {
            return Number.isFinite(v);
        })
        : undefined;
    const stringRecord = (key) => {
        const raw = body[key];
        if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
            return undefined;
        }
        const entries = Object.entries(raw).filter((entry) => {
            return typeof entry[1] === "string";
        });
        if (entries.length === 0) {
            return undefined;
        }
        return Object.fromEntries(entries);
    };
    return {
        serviceIds,
        entityKeys: stringArray("entityKeys"),
        statusCodes,
        spanKinds: stringArray("spanKinds"),
        spanNames: stringArray("spanNames"),
        /*
         * spanNameSearches is the only multiplicative filter (one ILIKE
         * predicate per entry) — cap it. The dashboard sends at most one.
         */
        spanNameSearches: (_a = stringArray("spanNameSearches")) === null || _a === void 0 ? void 0 : _a.slice(0, 10),
        spanIds: stringArray("spanIds"),
        traceIds: stringArray("traceIds"),
        nameSearchText: typeof body["nameSearchText"] === "string" && body["nameSearchText"]
            ? body["nameSearchText"]
            : undefined,
        statusMessageSearchText: typeof body["statusMessageSearchText"] === "string" &&
            body["statusMessageSearchText"]
            ? body["statusMessageSearchText"]
            : undefined,
        statusMessages: stringArray("statusMessages"),
        /*
         * Strict boolean check — unlike rootOnly, a coerced `false` is a
         * meaningful predicate here (JSON null must mean "no filter", not
         * "exclude exception spans").
         */
        hasException: typeof body["hasException"] === "boolean"
            ? body["hasException"]
            : undefined,
        minDurationNano: typeof body["minDurationNano"] === "number"
            ? body["minDurationNano"]
            : undefined,
        maxDurationNano: typeof body["maxDurationNano"] === "number"
            ? body["maxDurationNano"]
            : undefined,
        exactDurationNano: typeof body["exactDurationNano"] === "number"
            ? body["exactDurationNano"]
            : undefined,
        rootOnly: body["rootOnly"] === undefined ? undefined : Boolean(body["rootOnly"]),
        attributes: stringRecord("attributes"),
        attributeSearches: stringRecord("attributeSearches"),
    };
}
// --- Trace Histogram Endpoint ---
router.post("/telemetry/traces/histogram", ...requireTraceReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const bucketSizeInMinutes = body["bucketSizeInMinutes"] ||
            computeDefaultBucketSize(startTime, endTime);
        const traceFilters = parseTraceFilterBody(body);
        const request = Object.assign({ projectId: databaseProps.tenantId, startTime,
            endTime,
            bucketSizeInMinutes }, traceFilters);
        const buckets = await TraceAggregationService.getHistogram(request);
        return Response.sendJsonObjectResponse(req, res, {
            buckets: buckets,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Trace Facets Endpoint ---
router.post("/telemetry/traces/facets", ...requireTraceReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const facetKeys = body["facetKeys"]
            ? body["facetKeys"]
            : ["primaryEntityId", "statusCode", "kind", "name"];
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const limit = body["limit"] || 500;
        const traceFilters = parseTraceFilterBody(body);
        /*
         * Per-facet partial-match filter applied at the Postgres source-of-truth
         * lookup stage. Only consulted for resource facets (primaryEntityId /
         * hostId / dockerHostId / kubernetesClusterId) — other facets continue
         * to filter client-side over the loaded value list.
         */
        const facetSearchText = body["facetSearchText"]
            ? body["facetSearchText"]
            : undefined;
        /*
         * Shared window + active filters for both facet-counting paths below:
         * the exact projection-backed GROUP BY (resource facets + statusCode)
         * and the recent-N sample (kind + attribute facets, which have no cheap
         * exact path).
         */
        const multiRequest = Object.assign({ projectId: databaseProps.tenantId, startTime,
            endTime,
            facetKeys,
            limit }, traceFilters);
        /*
         * Resource facets (primaryEntityId / hostId / dockerHostId / k8s
         * cluster ...) and statusCode are counted with an exact,
         * projection-backed GROUP BY
         * in getResourceFacetCounts(). The recent-N sample below saturates with
         * whichever service is chattiest right now and reports 0 for every other
         * service regardless of its true volume over the window — the "top 1000"
         * symptom. Facets with no projection (kind, attribute keys) have no cheap
         * exact path and stay on the sample.
         */
        const sampledKeys = facetKeys.filter((key) => {
            return (!ResourceFacetResolver.isResourceFacet(key) && key !== "statusCode");
        });
        let facets = {};
        if (sampledKeys.length > 0) {
            try {
                facets = await TraceAggregationService.getFacetValuesFromSample(Object.assign(Object.assign({}, multiRequest), { facetKeys: sampledKeys }));
            }
            catch (_a) {
                facets = Object.fromEntries(sampledKeys.map((key) => {
                    return [key, []];
                }));
            }
        }
        const needsAccurateCounts = facetKeys.includes("statusCode") ||
            facetKeys.some((key) => {
                return ResourceFacetResolver.isResourceFacet(key);
            });
        let serviceCounts = new Map();
        let statusCounts = new Map();
        if (needsAccurateCounts) {
            try {
                const accurate = await TraceAggregationService.getResourceFacetCounts(multiRequest);
                serviceCounts = accurate.serviceCounts;
                statusCounts = accurate.statusCounts;
            }
            catch (_b) {
                /*
                 * Degrade gracefully: resource facets still enumerate via Postgres
                 * (count 0), statusCode falls back to empty.
                 */
            }
        }
        if (facetKeys.includes("statusCode")) {
            facets["statusCode"] = Array.from(statusCounts.entries())
                .map(([value, count]) => {
                return { value, count };
            })
                .sort((a, b) => {
                return b.count - a.count;
            })
                .slice(0, limit);
        }
        /*
         * Replace resource-facet results with the Postgres source-of-truth list
         * (filtered by facetSearchText and enriched with displayName). Every
         * resource facet shares the same exact primaryEntityId -> count map;
         * resource ids are globally unique, so each facet only ever resolves its own
         * entities. Entities with no telemetry in the window surface with count
         * 0 instead of being hidden, and the search box can find resources
         * beyond the loaded subset.
         */
        const resourceSpecs = facetKeys
            .filter((key) => {
            return ResourceFacetResolver.isResourceFacet(key);
        })
            .map((key) => {
            return {
                facetKey: key,
                counts: serviceCounts,
                searchText: facetSearchText === null || facetSearchText === void 0 ? void 0 : facetSearchText[key],
                limit,
            };
        });
        if (resourceSpecs.length > 0) {
            const resolved = await ResourceFacetResolver.resolve(databaseProps.tenantId, resourceSpecs);
            for (const key of Object.keys(resolved)) {
                facets[key] = resolved[key];
            }
        }
        return Response.sendJsonObjectResponse(req, res, {
            facets: facets,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Trace Analytics Endpoint ---
router.post("/telemetry/traces/analytics", ...requireTraceReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const chartType = body["chartType"] || "timeseries";
        if (!["timeseries", "toplist", "table"].includes(chartType)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid chartType"));
        }
        const metric = body["metric"] || "count";
        if (!TraceAggregationService.isValidAnalyticsMetric(metric)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid metric"));
        }
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const rawBucketSize = Number(body["bucketSizeInMinutes"]);
        const bucketSizeInMinutes = Number.isFinite(rawBucketSize) && rawBucketSize >= 1
            ? Math.trunc(rawBucketSize)
            : computeDefaultBucketSize(startTime, endTime);
        const groupBy = Array.isArray(body["groupBy"])
            ? body["groupBy"].filter((v) => {
                return typeof v === "string" && v.length > 0;
            })
            : undefined;
        /*
         * Clamp to a sane integer range — `limit` flows into LIMIT and the
         * timeseries series cap, so negative/fractional values would 500 in
         * ClickHouse and huge values would explode the result set. Numeric
         * strings are accepted (dashboard widget arguments are stored as
         * strings).
         */
        const rawLimit = Number(body["limit"]);
        const limit = Number.isFinite(rawLimit)
            ? Math.min(Math.max(Math.trunc(rawLimit), 1), 1000)
            : undefined;
        const traceFilters = parseTraceFilterBody(body);
        const request = Object.assign({ projectId: databaseProps.tenantId, startTime,
            endTime,
            bucketSizeInMinutes,
            chartType,
            metric,
            groupBy,
            limit }, traceFilters);
        if (chartType === "timeseries") {
            const data = await TraceAggregationService.getAnalyticsTimeseries(request);
            return Response.sendJsonObjectResponse(req, res, {
                data: data,
            });
        }
        if (chartType === "toplist") {
            const data = await TraceAggregationService.getAnalyticsTopList(request);
            return Response.sendJsonObjectResponse(req, res, {
                data: data,
            });
        }
        const data = await TraceAggregationService.getAnalyticsTable(request);
        return Response.sendJsonObjectResponse(req, res, {
            data: data,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Exception Histogram Endpoint ---
router.post("/telemetry/exceptions/histogram", ...requireExceptionReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -24);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const bucketSizeInMinutes = body["bucketSizeInMinutes"] ||
            computeDefaultBucketSize(startTime, endTime);
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const exceptionTypes = body["exceptionTypes"]
            ? body["exceptionTypes"]
            : undefined;
        const environments = body["environments"]
            ? body["environments"]
            : undefined;
        const fingerprints = body["fingerprints"]
            ? body["fingerprints"]
            : undefined;
        const traceIds = body["traceIds"]
            ? body["traceIds"]
            : undefined;
        const escaped = body["escaped"] === undefined ? undefined : Boolean(body["escaped"]);
        const messageSearchText = body["messageSearchText"]
            ? body["messageSearchText"]
            : undefined;
        const request = {
            projectId: databaseProps.tenantId,
            startTime,
            endTime,
            bucketSizeInMinutes,
            serviceIds,
            exceptionTypes,
            environments,
            fingerprints,
            traceIds,
            escaped,
            messageSearchText,
        };
        const buckets = await ExceptionAggregationService.getHistogram(request);
        return Response.sendJsonObjectResponse(req, res, {
            buckets: buckets,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Exception Facets Endpoint ---
router.post("/telemetry/exceptions/facets", ...requireExceptionReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const facetKeys = body["facetKeys"]
            ? body["facetKeys"]
            : [
                "primaryEntityId",
                "hostId",
                "dockerHostId",
                "kubernetesClusterId",
                "exceptionType",
                "environment",
            ];
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -24);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const limit = body["limit"] || 500;
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const exceptionTypes = body["exceptionTypes"]
            ? body["exceptionTypes"]
            : undefined;
        const environments = body["environments"]
            ? body["environments"]
            : undefined;
        const fingerprints = body["fingerprints"]
            ? body["fingerprints"]
            : undefined;
        const traceIds = body["traceIds"]
            ? body["traceIds"]
            : undefined;
        const escaped = body["escaped"] === undefined ? undefined : Boolean(body["escaped"]);
        const messageSearchText = body["messageSearchText"]
            ? body["messageSearchText"]
            : undefined;
        /*
         * Per-facet partial-match filter applied at the Postgres source-of-truth
         * lookup stage. Only consulted for resource facets — other facets
         * continue to filter client-side over the loaded value list.
         */
        const facetSearchText = body["facetSearchText"]
            ? body["facetSearchText"]
            : undefined;
        const projectId = databaseProps.tenantId;
        /*
         * Per-facet ClickHouse query in parallel. Per-facet errors degrade
         * gracefully to [] so a slow / failing facet can't block the others.
         */
        const facetResults = await Promise.all(facetKeys.map(async (facetKey) => {
            try {
                const request = {
                    projectId,
                    startTime,
                    endTime,
                    facetKey,
                    limit,
                    serviceIds,
                    exceptionTypes,
                    environments,
                    fingerprints,
                    traceIds,
                    escaped,
                    messageSearchText,
                };
                const values = await ExceptionAggregationService.getFacetValues(request);
                return [facetKey, values];
            }
            catch (_a) {
                return [facetKey, []];
            }
        }));
        const facets = Object.fromEntries(facetResults);
        /*
         * Replace resource-facet results with the Postgres source-of-truth list
         * (filtered by facetSearchText and enriched with displayName). Same
         * pattern as the trace/log facets endpoints.
         */
        const resourceSpecs = facetKeys
            .filter((key) => {
            return ResourceFacetResolver.isResourceFacet(key);
        })
            .map((key) => {
            const counts = new Map();
            for (const fv of facets[key] || []) {
                counts.set(fv.value, fv.count);
            }
            return {
                facetKey: key,
                counts,
                searchText: facetSearchText === null || facetSearchText === void 0 ? void 0 : facetSearchText[key],
                limit,
            };
        });
        if (resourceSpecs.length > 0) {
            const resolved = await ResourceFacetResolver.resolve(projectId, resourceSpecs);
            for (const key of Object.keys(resolved)) {
                facets[key] = resolved[key];
            }
        }
        return Response.sendJsonObjectResponse(req, res, {
            facets: facets,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Metric Facets Endpoint ---
router.post("/telemetry/metrics/facets", ...requireMetricReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const facetKeys = body["facetKeys"]
            ? body["facetKeys"]
            : ["primaryEntityId", "hostId", "dockerHostId", "kubernetesClusterId"];
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const limit = body["limit"] || 500;
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const metricNames = body["metricNames"]
            ? body["metricNames"]
            : undefined;
        const facetSearchText = body["facetSearchText"]
            ? body["facetSearchText"]
            : undefined;
        const projectId = databaseProps.tenantId;
        /*
         * Per-facet ClickHouse GROUP BY in parallel. Per-facet errors degrade
         * to [] so a slow facet doesn't block the rest.
         */
        const facetResults = await Promise.all(facetKeys.map(async (facetKey) => {
            try {
                const request = {
                    projectId,
                    startTime,
                    endTime,
                    facetKey,
                    limit,
                    serviceIds,
                    metricNames,
                };
                const values = await MetricAggregationService.getFacetValues(request);
                return [facetKey, values];
            }
            catch (_a) {
                return [facetKey, []];
            }
        }));
        const facets = Object.fromEntries(facetResults);
        /*
         * Replace resource-facet results with the Postgres source-of-truth list
         * (filtered by facetSearchText and enriched with displayName). Same
         * pattern as the trace / log / exception facets endpoints.
         */
        const resourceSpecs = facetKeys
            .filter((key) => {
            return ResourceFacetResolver.isResourceFacet(key);
        })
            .map((key) => {
            const counts = new Map();
            for (const fv of facets[key] || []) {
                counts.set(fv.value, fv.count);
            }
            return {
                facetKey: key,
                counts,
                searchText: facetSearchText === null || facetSearchText === void 0 ? void 0 : facetSearchText[key],
                limit,
            };
        });
        if (resourceSpecs.length > 0) {
            const resolved = await ResourceFacetResolver.resolve(projectId, resourceSpecs);
            for (const key of Object.keys(resolved)) {
                facets[key] = resolved[key];
            }
        }
        return Response.sendJsonObjectResponse(req, res, {
            facets: facets,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Log Analytics Endpoint ---
router.post("/telemetry/logs/analytics", ...requireLogReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const chartType = body["chartType"] || "timeseries";
        if (!["timeseries", "toplist", "table"].includes(chartType)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid chartType"));
        }
        const aggregation = body["aggregation"] || "count";
        if (!["count", "unique"].includes(aggregation)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid aggregation"));
        }
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const bucketSizeInMinutes = body["bucketSizeInMinutes"] ||
            computeDefaultBucketSize(startTime, endTime);
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const severityTexts = body["severityTexts"]
            ? body["severityTexts"]
            : undefined;
        const bodySearchText = body["bodySearchText"]
            ? body["bodySearchText"]
            : undefined;
        const traceIds = body["traceIds"]
            ? body["traceIds"]
            : undefined;
        const spanIds = body["spanIds"]
            ? body["spanIds"]
            : undefined;
        const groupBy = body["groupBy"]
            ? body["groupBy"]
            : undefined;
        const aggregationField = body["aggregationField"]
            ? body["aggregationField"]
            : undefined;
        const limit = body["limit"]
            ? body["limit"]
            : undefined;
        const request = {
            projectId: databaseProps.tenantId,
            startTime,
            endTime,
            bucketSizeInMinutes,
            chartType,
            groupBy,
            aggregation,
            aggregationField,
            serviceIds,
            severityTexts,
            bodySearchText,
            traceIds,
            spanIds,
            limit,
        };
        if (chartType === "timeseries") {
            const data = await LogAggregationService.getAnalyticsTimeseries(request);
            return Response.sendJsonObjectResponse(req, res, {
                data: data,
            });
        }
        if (chartType === "toplist") {
            const data = await LogAggregationService.getAnalyticsTopList(request);
            return Response.sendJsonObjectResponse(req, res, {
                data: data,
            });
        }
        // table
        const data = await LogAggregationService.getAnalyticsTable(request);
        return Response.sendJsonObjectResponse(req, res, {
            data: data,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Log Export Endpoint ---
router.post("/telemetry/logs/export", ...requireLogReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const limit = Math.min(body["limit"] || 10000, 10000);
        const format = body["format"] || "json";
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const severityTexts = body["severityTexts"]
            ? body["severityTexts"]
            : undefined;
        const bodySearchText = body["bodySearchText"]
            ? body["bodySearchText"]
            : undefined;
        const traceIds = body["traceIds"]
            ? body["traceIds"]
            : undefined;
        const spanIds = body["spanIds"]
            ? body["spanIds"]
            : undefined;
        const rows = await LogAggregationService.getExportLogs({
            projectId: databaseProps.tenantId,
            startTime,
            endTime,
            limit,
            serviceIds,
            severityTexts,
            bodySearchText,
            traceIds,
            spanIds,
        });
        if (format === "csv") {
            const header = "time,primaryEntityId,severityText,severityNumber,body,traceId,spanId,attributes";
            const csvRows = rows.map((row) => {
                const escapeCsv = (val) => {
                    const str = val === null || val === undefined ? "" : String(val);
                    if (str.includes(",") || str.includes('"') || str.includes("\n")) {
                        return `"${str.replace(/"/g, '""')}"`;
                    }
                    return str;
                };
                return [
                    escapeCsv(row["time"]),
                    escapeCsv(row["primaryEntityId"]),
                    escapeCsv(row["severityText"]),
                    escapeCsv(row["severityNumber"]),
                    escapeCsv(row["body"]),
                    escapeCsv(row["traceId"]),
                    escapeCsv(row["spanId"]),
                    escapeCsv(JSON.stringify(row["attributes"] || {})),
                ].join(",");
            });
            const csv = [header, ...csvRows].join("\n");
            res.setHeader("Content-Type", "text/csv; charset=utf-8");
            res.setHeader("Content-Disposition", "attachment; filename=logs-export.csv");
            res.status(200).send(csv);
            return;
        }
        // JSON format
        res.setHeader("Content-Type", "application/json; charset=utf-8");
        res.setHeader("Content-Disposition", "attachment; filename=logs-export.json");
        res.status(200).send(JSON.stringify(rows, null, 2));
    }
    catch (err) {
        next(err);
    }
});
// --- Log Context Endpoint ---
router.post("/telemetry/logs/context", ...requireLogReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const logId = body["logId"];
        // `serviceId` is the pre-rename alias kept for stale clients.
        const primaryEntityId = (body["primaryEntityId"] ||
            body["serviceId"]);
        const time = body["time"];
        if (!logId || !primaryEntityId || !time) {
            return Response.sendErrorResponse(req, res, new BadDataException("logId, primaryEntityId, and time are required"));
        }
        const count = body["count"] || 5;
        const result = await LogAggregationService.getLogContext({
            projectId: databaseProps.tenantId,
            primaryEntityId: new ObjectID(primaryEntityId),
            time: OneUptimeDate.fromString(time),
            logId,
            count,
        });
        return Response.sendJsonObjectResponse(req, res, {
            before: result.before,
            after: result.after,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Drop Filter Estimate Endpoint ---
router.post("/telemetry/logs/drop-filter-estimate", ...requireLogReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const filterQuery = body["filterQuery"];
        if (!filterQuery) {
            return Response.sendErrorResponse(req, res, new BadDataException("filterQuery is required"));
        }
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -24);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const severityTexts = body["severityTexts"]
            ? body["severityTexts"]
            : undefined;
        const result = await LogAggregationService.getDropFilterEstimate({
            projectId: databaseProps.tenantId,
            startTime,
            endTime,
            filterQuery,
            serviceIds,
            severityTexts,
        });
        return Response.sendJsonObjectResponse(req, res, {
            totalLogs: result.totalLogs,
            matchingLogs: result.matchingLogs,
            estimatedReductionPercent: result.estimatedReductionPercent,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Helpers ---
function computeDefaultBucketSize(startTime, endTime) {
    const diffMs = endTime.getTime() - startTime.getTime();
    const diffMinutes = diffMs / (1000 * 60);
    if (diffMinutes <= 60) {
        return 1;
    }
    if (diffMinutes <= 360) {
        return 5;
    }
    if (diffMinutes <= 1440) {
        return 15;
    }
    if (diffMinutes <= 10080) {
        return 60;
    }
    if (diffMinutes <= 43200) {
        return 360;
    }
    return 1440;
}
// --- Profile Get Attributes Endpoint ---
router.post("/telemetry/profiles/get-attributes", ...requireProfileReadAccess, async (req, res, next) => {
    return getAttributes(req, res, next, TelemetryType.Profile);
});
// --- Profile Flamegraph Endpoint ---
router.post("/telemetry/profiles/flamegraph", ...requireProfileReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const profileId = body["profileId"]
            ? body["profileId"]
            : undefined;
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : undefined;
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : undefined;
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const profileType = body["profileType"]
            ? body["profileType"]
            : undefined;
        const profileTypes = Array.isArray(body["profileTypes"])
            ? body["profileTypes"].filter((t) => {
                return typeof t === "string" && t.length > 0;
            })
            : undefined;
        if (!profileId && !startTime) {
            return Response.sendErrorResponse(req, res, new BadDataException("Either profileId or startTime must be provided"));
        }
        const request = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ projectId: databaseProps.tenantId }, (profileId !== undefined && { profileId })), (startTime !== undefined && { startTime })), (endTime !== undefined && { endTime })), (serviceIds !== undefined && { serviceIds })), (profileType !== undefined && { profileType })), (profileTypes !== undefined &&
            profileTypes.length > 0 && { profileTypes }));
        const result = await ProfileAggregationService.getFlamegraph(request);
        return Response.sendJsonObjectResponse(req, res, {
            flamegraph: result.flamegraph,
            truncated: result.truncated,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Profile Function List Endpoint ---
router.post("/telemetry/profiles/function-list", ...requireProfileReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const profileId = body["profileId"]
            ? body["profileId"]
            : undefined;
        /*
         * Only default the window when no profileId is given: a profile's
         * samples are bounded by the profile itself, and a defaulted
         * last-hour window would silently exclude any profile captured
         * before the window started.
         */
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : profileId
                ? undefined
                : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : profileId
                ? undefined
                : OneUptimeDate.getCurrentDate();
        if (!profileId && !startTime) {
            return Response.sendErrorResponse(req, res, new BadDataException("Either profileId or startTime must be provided"));
        }
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const profileType = body["profileType"]
            ? body["profileType"]
            : undefined;
        const profileTypes = Array.isArray(body["profileTypes"])
            ? body["profileTypes"].filter((t) => {
                return typeof t === "string" && t.length > 0;
            })
            : undefined;
        const limit = body["limit"]
            ? body["limit"]
            : undefined;
        const sortBy = body["sortBy"]
            ? body["sortBy"]
            : undefined;
        const request = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ projectId: databaseProps.tenantId }, (startTime !== undefined && { startTime })), (endTime !== undefined && { endTime })), (profileId !== undefined && { profileId })), (serviceIds !== undefined && { serviceIds })), (profileType !== undefined && { profileType })), (profileTypes !== undefined &&
            profileTypes.length > 0 && { profileTypes })), (limit !== undefined && { limit })), (sortBy !== undefined && { sortBy }));
        const result = await ProfileAggregationService.getFunctionList(request);
        return Response.sendJsonObjectResponse(req, res, {
            functions: result.functions,
            windowTotal: result.windowTotal,
            truncated: result.truncated,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Profile Service Activity Endpoint ---
router.post("/telemetry/profiles/service-activity", ...requireProfileReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : OneUptimeDate.addRemoveHours(OneUptimeDate.getCurrentDate(), -1);
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : OneUptimeDate.getCurrentDate();
        const profileType = body["profileType"]
            ? body["profileType"]
            : undefined;
        const profileTypes = Array.isArray(body["profileTypes"])
            ? body["profileTypes"].filter((t) => {
                return typeof t === "string" && t.length > 0;
            })
            : undefined;
        const request = Object.assign(Object.assign({ projectId: databaseProps.tenantId, startTime,
            endTime }, (profileType !== undefined && { profileType })), (profileTypes !== undefined &&
            profileTypes.length > 0 && { profileTypes }));
        const activity = await ProfileAggregationService.getServiceActivity(request);
        return Response.sendJsonObjectResponse(req, res, {
            activity: activity,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Profile pprof Export Endpoint ---
router.get("/telemetry/profiles/:profileId/pprof", ...requireProfileReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const profileId = req.params["profileId"];
        if (!profileId) {
            return Response.sendErrorResponse(req, res, new BadDataException("profileId is required"));
        }
        // Fetch profile metadata
        const profiles = await ProfileService.findBy({
            query: {
                projectId: databaseProps.tenantId,
                profileId: profileId,
            },
            select: {
                profileId: true,
                profileType: true,
                unit: true,
                periodType: true,
                period: true,
                startTime: true,
                endTime: true,
                durationNano: true,
            },
            limit: 1,
            skip: 0,
            props: {
                isRoot: true,
            },
        });
        if (!profiles[0]) {
            return Response.sendErrorResponse(req, res, new BadDataException("Profile not found"));
        }
        const profile = profiles[0];
        // Fetch profile samples
        const samplesResult = await ProfileSampleService.findBy({
            query: {
                projectId: databaseProps.tenantId,
                profileId: profileId,
            },
            select: {
                stacktrace: true,
                value: true,
                labels: true,
            },
            limit: 50000,
            skip: 0,
            sort: {
                value: SortOrder.Descending,
            },
            props: {
                isRoot: true,
            },
        });
        const pprofSamples = samplesResult.map((sample) => {
            return {
                stacktrace: sample.stacktrace || [],
                value: sample.value || 0,
                labels: sample.labels,
            };
        });
        const pprofProfile = {
            profileId: profile.profileId || profileId,
            profileType: profile.profileType || "cpu",
            unit: profile.unit || "nanoseconds",
            periodType: profile.periodType || "cpu",
            period: profile.period || 0,
            startTimeNanos: profile.startTime
                ? new Date(profile.startTime).getTime() * 1000000
                : 0,
            endTimeNanos: profile.endTime
                ? new Date(profile.endTime).getTime() * 1000000
                : 0,
            durationNanos: profile.durationNano || 0,
            samples: pprofSamples,
        };
        const compressed = await PprofEncoder.encodeAndCompress(pprofProfile);
        res.setHeader("Content-Type", "application/x-protobuf");
        res.setHeader("Content-Disposition", `attachment; filename=profile-${profileId}.pb.gz`);
        res.setHeader("Content-Length", compressed.length.toString());
        res.send(compressed);
    }
    catch (err) {
        next(err);
    }
});
// --- Profile Diff Flamegraph Endpoint ---
router.post("/telemetry/profiles/diff-flamegraph", ...requireProfileReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const baselineStartTime = body["baselineStartTime"]
            ? OneUptimeDate.fromString(body["baselineStartTime"])
            : undefined;
        const baselineEndTime = body["baselineEndTime"]
            ? OneUptimeDate.fromString(body["baselineEndTime"])
            : undefined;
        const comparisonStartTime = body["comparisonStartTime"]
            ? OneUptimeDate.fromString(body["comparisonStartTime"])
            : undefined;
        const comparisonEndTime = body["comparisonEndTime"]
            ? OneUptimeDate.fromString(body["comparisonEndTime"])
            : undefined;
        if (!baselineStartTime ||
            !baselineEndTime ||
            !comparisonStartTime ||
            !comparisonEndTime) {
            return Response.sendErrorResponse(req, res, new BadDataException("baselineStartTime, baselineEndTime, comparisonStartTime, and comparisonEndTime are all required"));
        }
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const profileType = body["profileType"]
            ? body["profileType"]
            : undefined;
        const profileTypes = Array.isArray(body["profileTypes"])
            ? body["profileTypes"].filter((t) => {
                return typeof t === "string" && t.length > 0;
            })
            : undefined;
        const request = Object.assign(Object.assign(Object.assign({ projectId: databaseProps.tenantId, baselineStartTime,
            baselineEndTime,
            comparisonStartTime,
            comparisonEndTime }, (serviceIds !== undefined && { serviceIds })), (profileType !== undefined && { profileType })), (profileTypes !== undefined &&
            profileTypes.length > 0 && { profileTypes }));
        const result = await ProfileAggregationService.getDiffFlamegraph(request);
        /*
         * `truncated` is surfaced so the UI can warn that the diff was built
         * from a capped sample set rather than the full window.
         */
        return Response.sendJsonObjectResponse(req, res, {
            diffFlamegraph: result.diffFlamegraph,
            truncated: result.truncated,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Profile Function Focus Endpoint ---
router.post("/telemetry/profiles/function-focus", ...requireProfileReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const functionName = body["functionName"] && typeof body["functionName"] === "string"
            ? body["functionName"]
            : undefined;
        if (!functionName) {
            return Response.sendErrorResponse(req, res, new BadDataException("functionName is required"));
        }
        /*
         * fileName participates in frame identity (frames match on
         * functionName + fileName; line numbers are ignored so identity
         * survives deploys) but may legitimately be empty: folded uploads
         * produce bare frames with no file information.
         */
        const fileName = typeof body["fileName"] === "string"
            ? body["fileName"]
            : "";
        const profileId = body["profileId"]
            ? body["profileId"]
            : undefined;
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : undefined;
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : undefined;
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const profileType = body["profileType"]
            ? body["profileType"]
            : undefined;
        const profileTypes = Array.isArray(body["profileTypes"])
            ? body["profileTypes"].filter((t) => {
                return typeof t === "string" && t.length > 0;
            })
            : undefined;
        if (!profileId && !startTime) {
            return Response.sendErrorResponse(req, res, new BadDataException("Either profileId or startTime must be provided"));
        }
        const request = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ projectId: databaseProps.tenantId, functionName,
            fileName }, (profileId !== undefined && { profileId })), (startTime !== undefined && { startTime })), (endTime !== undefined && { endTime })), (serviceIds !== undefined && { serviceIds })), (profileType !== undefined && { profileType })), (profileTypes !== undefined &&
            profileTypes.length > 0 && { profileTypes }));
        const result = await ProfileAggregationService.getFunctionFocus(request);
        return Response.sendJsonObjectResponse(req, res, {
            functionName: result.functionName,
            fileName: result.fileName,
            totalValue: result.totalValue,
            selfValue: result.selfValue,
            sampleCount: result.sampleCount,
            windowTotal: result.windowTotal,
            callers: result.callers,
            callees: result.callees,
            truncated: result.truncated,
        });
    }
    catch (err) {
        next(err);
    }
});
// --- Profile Breakdown Endpoint ---
router.post("/telemetry/profiles/breakdown", ...requireProfileReadAccess, async (req, res, next) => {
    try {
        const databaseProps = await CommonAPI.getDatabaseCommonInteractionProps(req);
        if (!(databaseProps === null || databaseProps === void 0 ? void 0 : databaseProps.tenantId)) {
            return Response.sendErrorResponse(req, res, new BadDataException("Invalid Project ID"));
        }
        const body = req.body;
        const startTime = body["startTime"]
            ? OneUptimeDate.fromString(body["startTime"])
            : undefined;
        const endTime = body["endTime"]
            ? OneUptimeDate.fromString(body["endTime"])
            : undefined;
        /*
         * breakdownBy is either the reserved key 'service' (grouping by
         * primaryEntityId, resolved to display names by the UI) or a Profile
         * attribute key.
         */
        const breakdownBy = body["breakdownBy"] && typeof body["breakdownBy"] === "string"
            ? body["breakdownBy"]
            : undefined;
        if (!startTime || !endTime || !breakdownBy) {
            return Response.sendErrorResponse(req, res, new BadDataException("startTime, endTime, and breakdownBy are all required"));
        }
        const serviceIds = body["serviceIds"]
            ? body["serviceIds"].map((id) => {
                return new ObjectID(id);
            })
            : undefined;
        const profileType = body["profileType"]
            ? body["profileType"]
            : undefined;
        const profileTypes = Array.isArray(body["profileTypes"])
            ? body["profileTypes"].filter((t) => {
                return typeof t === "string" && t.length > 0;
            })
            : undefined;
        const limit = body["limit"]
            ? body["limit"]
            : undefined;
        const request = Object.assign(Object.assign(Object.assign(Object.assign({ projectId: databaseProps.tenantId, startTime,
            endTime,
            breakdownBy }, (serviceIds !== undefined && { serviceIds })), (profileType !== undefined && { profileType })), (profileTypes !== undefined &&
            profileTypes.length > 0 && { profileTypes })), (limit !== undefined && { limit }));
        const result = await ProfileAggregationService.getBreakdown(request);
        return Response.sendJsonObjectResponse(req, res, {
            items: result.items,
            totalSampleCount: result.totalSampleCount,
        });
    }
    catch (err) {
        next(err);
    }
});
export default router;
//# sourceMappingURL=TelemetryAPI.js.map