"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.suppressHandleApplicationWarning = void 0;
exports.handleApplication = handleApplication;
exports.initUsageLicensing = initUsageLicensing;
exports.isActiveLicense = isActiveLicense;
exports.getActiveLicense = getActiveLicense;
exports.isLicensed = isLicensed;
exports.getUsageLicenses = getUsageLicenses;
const validation_ts_1 = require("./validation.js");
const hdbError_js_1 = require("../core/utility/errors/hdbError.js");
const write_ts_1 = require("../core/resources/analytics/write.js");
const transaction_ts_1 = require("../core/resources/transaction.js");
const databases_ts_1 = require("../core/resources/databases.js");
const chokidar_1 = require("chokidar");
const node_path_1 = __importDefault(require("node:path"));
const fs = __importStar(require("node:fs/promises"));
const configUtils = __importStar(require("../core/config/configUtils.js"));
const terms = __importStar(require("../core/utility/hdbTerms.js"));
const http_ts_1 = require("../core/server/http.js");
//
exports.suppressHandleApplicationWarning = true;
let logger;
class ExistingLicenseError extends Error {
}
let licenseRegion;
let licenseConsoleErrorPrinted = false;
let licenseWarningIntervalId;
const LICENSE_NAG_PERIOD = 600000; // ten minutes
function handleApplication({ server, logger, options }) {
    const region = options.get(terms.CONFIG_PARAMS.LICENSE_REGION.split('_'));
    const mode = options.get(terms.CONFIG_PARAMS.LICENSE_MODE.split('_'));
    initUsageLicensing({ server, logger, license: { region, mode } });
}
function initUsageLicensing(params) {
    logger = params.logger;
    (0, validation_ts_1.initPublicKey)(params.license.mode);
    licenseRegion = params.license.region;
    (0, write_ts_1.onAnalyticsAggregate)(recordUsage);
    server.registerOperation?.({
        name: 'install_usage_license',
        execute: installUsageLicenseOp,
        httpMethod: 'POST',
    });
    server.registerOperation?.({
        name: 'get_usage_licenses',
        execute: getUsageLicensesOp,
        httpMethod: 'GET',
    });
    const licensesPath = node_path_1.default.join(node_path_1.default.dirname(configUtils.getConfigFilePath()), 'licenses');
    const watchOptions = {
        persistent: false,
        ignoreInitial: false,
        depth: 0,
        ignored: (file, stats) => stats?.isFile() && !file.endsWith('.txt'),
    };
    (0, chokidar_1.watch)(licensesPath, watchOptions).on('add', loadLicenseFile);
}
async function installUsageLicenseOp(req) {
    const license = req.license;
    try {
        await installUsageLicense(license);
    }
    catch (cause) {
        const error = new hdbError_js_1.ClientError('Failed to install usage license; ' + cause.message, 409);
        error.cause = cause;
        throw error;
    }
    return 'Successfully installed usage license';
}
async function installUsageLicense(license) {
    const validatedLicense = (0, validation_ts_1.validateLicense)(license);
    const { id } = validatedLicense;
    const existingLicense = await databases_ts_1.databases.system.hdb_license.get(id);
    if (existingLicense) {
        throw new ExistingLicenseError(`A usage license with ${id} already exists`);
    }
    logger.info?.('Installing usage license:', validatedLicense);
    return databases_ts_1.databases.system.hdb_license.put(id, validatedLicense);
}
function isActiveLicense(license) {
    return ((license.reads === -1 || (license.usedReads ?? 0) < license.reads) &&
        (license.readBytes === -1 || (license.usedReadBytes ?? 0) < license.readBytes) &&
        (license.writes === -1 || (license.usedWrites ?? 0) < license.writes) &&
        (license.writeBytes === -1 || (license.usedWriteBytes ?? 0) < license.writeBytes) &&
        (license.realTimeMessages === -1 || (license.usedRealTimeMessages ?? 0) < license.realTimeMessages) &&
        (license.realTimeBytes === -1 || (license.usedRealTimeBytes ?? 0) < license.realTimeBytes) &&
        (license.cpuTime === -1 || (license.usedCpuTime ?? 0) < license.cpuTime) &&
        (license.storage === -1 || (license.usedStorage ?? 0) < license.storage));
}
async function getActiveLicense() {
    const licenseQuery = {
        sort: { attribute: '__createdtime__' },
        conditions: [{ attribute: 'expiration', comparator: 'greater_than', value: new Date().toISOString() }],
    };
    if (licenseRegion !== undefined) {
        licenseQuery.conditions.push({ attribute: 'region', comparator: 'equals', value: licenseRegion });
    }
    const results = databases_ts_1.databases.system.hdb_license?.search(licenseQuery);
    for await (const license of results ?? []) {
        if (isActiveLicense(license)) {
            return license;
        }
    }
    return undefined;
}
async function isLicensed() {
    const activeLicense = await getActiveLicense();
    return activeLicense !== undefined;
}
async function recordUsage(analytics) {
    logger.trace?.('Recording usage into license from analytics');
    const activeLicenseId = (await getActiveLicense())?.id;
    if (activeLicenseId) {
        logger.trace?.('Found license to record usage into:', activeLicenseId);
        const context = {};
        (0, transaction_ts_1.transaction)(context, () => {
            const updatableActiveLicense = databases_ts_1.databases.system.hdb_license.update(activeLicenseId, context);
            for (const analyticsRecord of analytics) {
                logger.trace?.('Processing analytics record:', analyticsRecord);
                switch (analyticsRecord.metric) {
                    case 'db-read':
                        logger.trace?.('Recording read usage into license');
                        updatableActiveLicense.addTo('usedReads', analyticsRecord.count);
                        updatableActiveLicense.addTo('usedReadBytes', analyticsRecord.mean * analyticsRecord.count);
                        break;
                    case 'db-write':
                        logger.trace?.('Recording write usage into license');
                        updatableActiveLicense.addTo('usedWrites', analyticsRecord.count);
                        updatableActiveLicense.addTo('usedWriteBytes', analyticsRecord.mean * analyticsRecord.count);
                        break;
                    case 'db-message':
                        logger.trace?.('Recording message usage into license');
                        updatableActiveLicense.addTo('usedRealTimeMessages', analyticsRecord.count);
                        updatableActiveLicense.addTo('usedRealTimeBytes', analyticsRecord.mean * analyticsRecord.count);
                        break;
                    case 'cpu-usage':
                        if (analyticsRecord.path === 'user') {
                            logger.trace?.('Recording CPU usage into license');
                            updatableActiveLicense.addTo('usedCpuTime', (analyticsRecord.mean * analyticsRecord.count) / 3600);
                        }
                        break;
                    default:
                        logger.trace?.('Skipping metric:', analyticsRecord.metric);
                }
            }
        });
        http_ts_1.universalHeaders.splice(0, http_ts_1.universalHeaders.length); // clear out any previous license nag headers
        if (licenseWarningIntervalId !== undefined) {
            clearInterval(licenseWarningIntervalId);
            licenseWarningIntervalId = undefined;
            licenseConsoleErrorPrinted = false;
        }
    }
    else {
        // assign license nag header
        http_ts_1.universalHeaders.splice(0, http_ts_1.universalHeaders.length, 'X-License-Info', 'Unlicensed Harper Pro, this should only be used for educational and development purposes');
        if (!process.env.DEV_MODE) {
            const msg = 'This server does not have valid usage licenses, this should only be used for educational and development purposes.';
            if (!licenseConsoleErrorPrinted) {
                console.error(msg);
                licenseConsoleErrorPrinted = true;
            }
            if (licenseWarningIntervalId === undefined) {
                licenseWarningIntervalId = setInterval(() => {
                    logger.notify?.(msg);
                }, LICENSE_NAG_PERIOD).unref();
            }
        }
    }
}
function getUsageLicensesOp(req) {
    const params = {};
    if (req.region) {
        params.region = req.region;
    }
    return getUsageLicenses(params);
}
function getUsageLicenses(params) {
    const conditions = [];
    const attrs = typeof params === 'object' ? Object.keys(params) : [];
    if (attrs.length > 0) {
        attrs.forEach((attribute) => {
            conditions.push({ attribute, comparator: 'equals', value: params[attribute] });
        });
    }
    return databases_ts_1.databases.system.hdb_license.search({
        sort: { attribute: '__createdtime__' },
        conditions,
    });
}
async function loadLicenseFile(filePath) {
    logger.trace?.('Loading usage license from file:', filePath);
    const encodedLicense = await fs.readFile(filePath, { encoding: 'utf-8' });
    try {
        await installUsageLicense(encodedLicense);
    }
    catch (err) {
        logger.error?.('Failed to install usage license from file:', filePath, err);
    }
}
//# sourceMappingURL=usageLicensing.js.map