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 DatabaseService from "./DatabaseService";
import ProjectService from "./ProjectService";
import ServiceLabelRuleEngineService from "./ServiceLabelRuleEngineService";
import ServiceOwnerRuleEngineService from "./ServiceOwnerRuleEngineService";
import ArrayUtil from "../../Utils/Array";
import { BrightColors } from "../../Types/BrandColors";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import OneUptimeDate from "../../Types/Date";
import Model from "../../Models/DatabaseModels/Service";
import GlobalCache from "../Infrastructure/GlobalCache";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import logger from "../Utils/Logger";
import crypto from "crypto";
const DEFAULT_TELEMETRY_RETENTION_IN_DAYS = 15;
const LAST_SEEN_CACHE_NAMESPACE = "service-last-seen";
const LAST_SEEN_THROTTLE_SECONDS = 60;
const LABELS_APPLIED_CACHE_NAMESPACE = "service-labels-applied";
const LABELS_APPLIED_CACHE_TTL_SECONDS = 60;
export class Service extends DatabaseService {
    constructor() {
        super(Model);
    }
    async onBeforeCreate(createBy) {
        // select a random color.
        createBy.data.serviceColor = ArrayUtil.selectItemByRandom(BrightColors);
        return {
            carryForward: null,
            createBy: createBy,
        };
    }
    async onCreateSuccess(_onCreate, createdItem) {
        if (createdItem.projectId && createdItem.id) {
            Promise.resolve()
                .then(async () => {
                await ServiceLabelRuleEngineService.applyRulesToService(createdItem);
            })
                .then(async () => {
                await ServiceOwnerRuleEngineService.applyRulesToService(createdItem);
            })
                .catch((error) => {
                var _a, _b;
                logger.error(`Error applying service rules in ServiceService.onCreateSuccess: ${error}`, {
                    projectId: (_a = createdItem.projectId) === null || _a === void 0 ? void 0 : _a.toString(),
                    serviceId: (_b = createdItem.id) === null || _b === void 0 ? void 0 : _b.toString(),
                });
            });
        }
        return createdItem;
    }
    async getTelemetryDataRetentionInDays(serviceId) {
        const service = await this.findOneById({
            id: serviceId,
            select: {
                projectId: true,
                retainTelemetryDataForDays: true,
            },
            props: {
                isRoot: true,
            },
        });
        if (!service) {
            throw new BadDataException("Service not found");
        }
        if (service.retainTelemetryDataForDays) {
            return service.retainTelemetryDataForDays;
        }
        // Fall back to project-level default.
        if (service.projectId) {
            const project = await ProjectService.findOneById({
                id: service.projectId,
                select: {
                    defaultTelemetryRetentionInDays: true,
                },
                props: {
                    isRoot: true,
                },
            });
            if (project === null || project === void 0 ? void 0 : project.defaultTelemetryRetentionInDays) {
                return project.defaultTelemetryRetentionInDays;
            }
        }
        return DEFAULT_TELEMETRY_RETENTION_IN_DAYS;
    }
    /*
     * Refresh `lastSeenAt` for a service. Throttled per-service so the
     * steady-state telemetry firehose (every metric/log/trace batch
     * re-resolves the same serviceId) costs one in-memory cache lookup
     * per batch instead of a DB write.
     */
    async updateLastSeen(serviceId, extra) {
        /*
         * Throttle keyed on a fingerprint of the metadata so the steady-state
         * firehose (the same serviceId re-resolved every batch) costs one
         * in-memory cache lookup, but a changed attribute (e.g. a new
         * service.version after a deploy) busts the cache and writes
         * immediately. With no extras the fingerprint is constant, preserving
         * the original "refresh lastSeenAt at most once per window" behaviour.
         */
        const cacheKey = serviceId.toString();
        const fingerprint = this.fingerprintLastSeenExtras(extra);
        const cached = await GlobalCache.getString(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
        if (cached === fingerprint) {
            return;
        }
        await GlobalCache.setString(LAST_SEEN_CACHE_NAMESPACE, cacheKey, fingerprint, {
            expiresInSeconds: LAST_SEEN_THROTTLE_SECONDS,
        });
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const data = {
            lastSeenAt: OneUptimeDate.getCurrentDate(),
        };
        if (extra === null || extra === void 0 ? void 0 : extra.serviceVersion) {
            data.serviceVersion = extra.serviceVersion;
        }
        if (extra === null || extra === void 0 ? void 0 : extra.deploymentEnvironment) {
            data.deploymentEnvironment = extra.deploymentEnvironment;
        }
        if (extra === null || extra === void 0 ? void 0 : extra.serviceNamespace) {
            data.serviceNamespace = extra.serviceNamespace;
        }
        if (extra === null || extra === void 0 ? void 0 : extra.runtimeName) {
            data.runtimeName = extra.runtimeName;
        }
        if (extra === null || extra === void 0 ? void 0 : extra.runtimeVersion) {
            data.runtimeVersion = extra.runtimeVersion;
        }
        if (extra === null || extra === void 0 ? void 0 : extra.telemetrySdkLanguage) {
            data.telemetrySdkLanguage = extra.telemetrySdkLanguage;
        }
        if (extra === null || extra === void 0 ? void 0 : extra.cloudProvider) {
            data.cloudProvider = extra.cloudProvider;
        }
        if (extra === null || extra === void 0 ? void 0 : extra.cloudPlatform) {
            data.cloudPlatform = extra.cloudPlatform;
        }
        if (extra === null || extra === void 0 ? void 0 : extra.cloudRegion) {
            data.cloudRegion = extra.cloudRegion;
        }
        if (extra === null || extra === void 0 ? void 0 : extra.cloudAccountId) {
            data.cloudAccountId = extra.cloudAccountId;
        }
        await this.updateOneById({
            id: serviceId,
            data: data,
            props: {
                isRoot: true,
            },
        });
    }
    fingerprintLastSeenExtras(extra) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
        const normalized = {
            serviceVersion: (_a = extra === null || extra === void 0 ? void 0 : extra.serviceVersion) !== null && _a !== void 0 ? _a : null,
            deploymentEnvironment: (_b = extra === null || extra === void 0 ? void 0 : extra.deploymentEnvironment) !== null && _b !== void 0 ? _b : null,
            serviceNamespace: (_c = extra === null || extra === void 0 ? void 0 : extra.serviceNamespace) !== null && _c !== void 0 ? _c : null,
            runtimeName: (_d = extra === null || extra === void 0 ? void 0 : extra.runtimeName) !== null && _d !== void 0 ? _d : null,
            runtimeVersion: (_e = extra === null || extra === void 0 ? void 0 : extra.runtimeVersion) !== null && _e !== void 0 ? _e : null,
            telemetrySdkLanguage: (_f = extra === null || extra === void 0 ? void 0 : extra.telemetrySdkLanguage) !== null && _f !== void 0 ? _f : null,
            cloudProvider: (_g = extra === null || extra === void 0 ? void 0 : extra.cloudProvider) !== null && _g !== void 0 ? _g : null,
            cloudPlatform: (_h = extra === null || extra === void 0 ? void 0 : extra.cloudPlatform) !== null && _h !== void 0 ? _h : null,
            cloudRegion: (_j = extra === null || extra === void 0 ? void 0 : extra.cloudRegion) !== null && _j !== void 0 ? _j : null,
            cloudAccountId: (_k = extra === null || extra === void 0 ? void 0 : extra.cloudAccountId) !== null && _k !== void 0 ? _k : null,
        };
        return crypto
            .createHash("sha1")
            .update(JSON.stringify(normalized))
            .digest("hex");
    }
    /**
     * Additively attach labels to a telemetry service. Existing labels
     * are never removed — manual labels set via the UI survive ingest.
     * The set of labelIds passed in is fingerprinted and cached for
     * 60s so the steady-state OTel collector pushing the same labels
     * every batch costs one in-memory lookup, not a join-table scan.
     */
    async attachLabels(data) {
        var _a;
        if (!data.labelIds || data.labelIds.length === 0) {
            return;
        }
        const cacheKey = data.serviceId.toString();
        const fingerprint = fingerprintLabelIds(data.labelIds);
        const cached = await GlobalCache.getString(LABELS_APPLIED_CACHE_NAMESPACE, cacheKey);
        if (cached === fingerprint) {
            return;
        }
        try {
            const serviceIdStr = data.serviceId.toString();
            const existingLabels = await this.getRepository()
                .createQueryBuilder()
                .relation(Model, "labels")
                .of(serviceIdStr)
                .loadMany();
            const existingIds = new Set();
            for (const lbl of existingLabels) {
                const idStr = (_a = lbl._id) === null || _a === void 0 ? void 0 : _a.toString();
                if (idStr) {
                    existingIds.add(idStr);
                }
            }
            const toAddIds = [];
            const seen = new Set();
            for (const id of data.labelIds) {
                const idStr = id.toString();
                if (existingIds.has(idStr) || seen.has(idStr)) {
                    continue;
                }
                seen.add(idStr);
                toAddIds.push(idStr);
            }
            if (toAddIds.length > 0) {
                await this.getRepository()
                    .createQueryBuilder()
                    .relation(Model, "labels")
                    .of(serviceIdStr)
                    .add(toAddIds);
            }
            await GlobalCache.setString(LABELS_APPLIED_CACHE_NAMESPACE, cacheKey, fingerprint, { expiresInSeconds: LABELS_APPLIED_CACHE_TTL_SECONDS });
        }
        catch (err) {
            logger.warn(`ServiceService.attachLabels failed for service ${data.serviceId.toString()}: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
}
__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, Model]),
    __metadata("design:returntype", Promise)
], Service.prototype, "onCreateSuccess", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID]),
    __metadata("design:returntype", Promise)
], Service.prototype, "getTelemetryDataRetentionInDays", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [ObjectID, Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "updateLastSeen", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], Service.prototype, "attachLabels", null);
function fingerprintLabelIds(labelIds) {
    const sorted = labelIds
        .map((id) => {
        return id.toString();
    })
        .sort();
    return crypto.createHash("sha1").update(sorted.join(",")).digest("hex");
}
export default new Service();
//# sourceMappingURL=ServiceService.js.map