"use strict";
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2024-2026 Gracker (Chris)
// This file is part of SmartPerfetto. See LICENSE for details.
Object.defineProperty(exports, "__esModule", { value: true });
exports.readWorkspaceEnterprisePolicies = readWorkspaceEnterprisePolicies;
exports.resolveEnterpriseRetentionExpiresAt = resolveEnterpriseRetentionExpiresAt;
exports.evaluateTraceUploadQuota = evaluateTraceUploadQuota;
exports.evaluateAnalysisRunQuota = evaluateAnalysisRunQuota;
const config_1 = require("../config");
const enterpriseDb_1 = require("./enterpriseDb");
const DAY_MS = 24 * 60 * 60 * 1000;
const ACTIVE_RUN_STATUSES = ['pending', 'running', 'awaiting_user'];
function allowedDecision() {
    return {
        allowed: true,
        code: 'OK',
        status: 'allowed',
        httpStatus: 200,
        message: 'Allowed',
        details: {},
    };
}
function denyDecision(code, status, httpStatus, message, details) {
    return {
        allowed: false,
        code,
        status,
        httpStatus,
        message,
        details,
    };
}
function parseObjectJson(value) {
    if (!value)
        return {};
    try {
        const parsed = JSON.parse(value);
        return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
            ? parsed
            : {};
    }
    catch {
        return {};
    }
}
function nonNegativeInteger(value) {
    if (typeof value !== 'number' || !Number.isFinite(value) || value < 0)
        return undefined;
    return Math.floor(value);
}
function normalizeQuotaPolicy(raw) {
    return {
        maxTraceBytes: nonNegativeInteger(raw.maxTraceBytes),
        maxWorkspaceTraceBytes: nonNegativeInteger(raw.maxWorkspaceTraceBytes),
        maxConcurrentRuns: nonNegativeInteger(raw.maxConcurrentRuns),
        monthlyRunLimit: nonNegativeInteger(raw.monthlyRunLimit),
    };
}
function normalizeRetentionPolicy(raw) {
    return {
        defaultRetentionDays: nonNegativeInteger(raw.defaultRetentionDays),
        traceRetentionDays: nonNegativeInteger(raw.traceRetentionDays),
        reportRetentionDays: nonNegativeInteger(raw.reportRetentionDays),
    };
}
function monthStartMs(now) {
    const date = new Date(now);
    return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1);
}
function workspaceTraceBytes(db, scope, now) {
    const row = db.prepare(`
    SELECT COALESCE(SUM(size_bytes), 0) AS total
    FROM trace_assets
    WHERE tenant_id = ?
      AND workspace_id = ?
      AND status <> 'deleted'
      AND (expires_at IS NULL OR expires_at > ?)
  `).get(scope.tenantId, scope.workspaceId, now);
    return row?.total ?? 0;
}
function activeRunCount(db, scope) {
    const placeholders = ACTIVE_RUN_STATUSES.map(() => '?').join(', ');
    const row = db.prepare(`
    SELECT COUNT(*) AS count
    FROM analysis_runs
    WHERE tenant_id = ?
      AND workspace_id = ?
      AND status IN (${placeholders})
  `).get(scope.tenantId, scope.workspaceId, ...ACTIVE_RUN_STATUSES);
    return row?.count ?? 0;
}
function monthlyRunCount(db, scope, now) {
    const row = db.prepare(`
    SELECT COUNT(*) AS count
    FROM analysis_runs
    WHERE tenant_id = ?
      AND workspace_id = ?
      AND started_at >= ?
  `).get(scope.tenantId, scope.workspaceId, monthStartMs(now));
    return row?.count ?? 0;
}
function readWorkspaceEnterprisePolicies(db, scope) {
    const row = db.prepare(`
    SELECT quota_policy, retention_policy
    FROM workspaces
    WHERE tenant_id = ?
      AND id = ?
    LIMIT 1
  `).get(scope.tenantId, scope.workspaceId);
    return {
        quotaPolicy: normalizeQuotaPolicy(parseObjectJson(row?.quota_policy ?? null)),
        retentionPolicy: normalizeRetentionPolicy(parseObjectJson(row?.retention_policy ?? null)),
    };
}
function resolveEnterpriseRetentionExpiresAt(db, scope, artifactType, createdAt) {
    const { retentionPolicy } = readWorkspaceEnterprisePolicies(db, scope);
    const days = artifactType === 'trace'
        ? retentionPolicy.traceRetentionDays ?? retentionPolicy.defaultRetentionDays
        : retentionPolicy.reportRetentionDays ?? retentionPolicy.defaultRetentionDays;
    if (days === undefined)
        return null;
    return createdAt + days * DAY_MS;
}
function evaluateTraceUploadQuota(context, sizeBytes, options = {}) {
    if (!(0, config_1.resolveFeatureConfig)().enterprise)
        return allowedDecision();
    const now = options.now ?? Date.now();
    const requestedBytes = Math.max(0, Math.floor(sizeBytes));
    const db = (0, enterpriseDb_1.openEnterpriseDb)();
    try {
        const scope = {
            tenantId: context.tenantId,
            workspaceId: context.workspaceId,
            userId: context.userId,
        };
        const { quotaPolicy } = readWorkspaceEnterprisePolicies(db, scope);
        if (quotaPolicy.maxTraceBytes !== undefined && requestedBytes > quotaPolicy.maxTraceBytes) {
            return denyDecision('TRACE_SIZE_QUOTA_EXCEEDED', 'quota_exceeded', 413, 'Trace upload exceeds workspace per-trace quota', {
                requestedBytes,
                limitBytes: quotaPolicy.maxTraceBytes,
            });
        }
        if (quotaPolicy.maxWorkspaceTraceBytes !== undefined) {
            const currentBytes = workspaceTraceBytes(db, scope, now);
            if (currentBytes + requestedBytes > quotaPolicy.maxWorkspaceTraceBytes) {
                return denyDecision('WORKSPACE_TRACE_STORAGE_QUOTA_EXCEEDED', 'quota_exceeded', 409, 'Trace upload exceeds workspace trace storage quota', {
                    currentBytes,
                    requestedBytes,
                    limitBytes: quotaPolicy.maxWorkspaceTraceBytes,
                });
            }
        }
        return allowedDecision();
    }
    finally {
        db.close();
    }
}
function evaluateAnalysisRunQuota(context, options = {}) {
    if (!(0, config_1.resolveFeatureConfig)().enterprise)
        return allowedDecision();
    const now = options.now ?? Date.now();
    const db = (0, enterpriseDb_1.openEnterpriseDb)();
    try {
        const scope = {
            tenantId: context.tenantId,
            workspaceId: context.workspaceId,
            userId: context.userId,
        };
        const { quotaPolicy } = readWorkspaceEnterprisePolicies(db, scope);
        if (quotaPolicy.monthlyRunLimit !== undefined) {
            const usedRuns = monthlyRunCount(db, scope, now);
            if (usedRuns >= quotaPolicy.monthlyRunLimit) {
                return denyDecision('MONTHLY_RUN_QUOTA_EXCEEDED', 'quota_exceeded', 402, 'Workspace monthly run quota is exhausted', {
                    usedRuns,
                    limitRuns: quotaPolicy.monthlyRunLimit,
                    windowStartMs: monthStartMs(now),
                });
            }
        }
        if (quotaPolicy.maxConcurrentRuns !== undefined) {
            const activeRuns = activeRunCount(db, scope);
            if (activeRuns >= quotaPolicy.maxConcurrentRuns) {
                return denyDecision('CONCURRENT_RUN_QUOTA_EXCEEDED', 'pending', 429, 'Workspace concurrent run quota is full', {
                    activeRuns,
                    limitRuns: quotaPolicy.maxConcurrentRuns,
                    queuePosition: activeRuns - quotaPolicy.maxConcurrentRuns + 1,
                });
            }
        }
        return allowedDecision();
    }
    finally {
        db.close();
    }
}
//# sourceMappingURL=enterpriseQuotaPolicyService.js.map