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);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
/**
 * Copyright (C) 2024 Hedera Hashgraph, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the ""License"");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an ""AS IS"" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
import fs from 'fs';
import path from 'path';
import { SoloError, IllegalArgumentError, MissingArgumentError } from './errors.js';
import * as yaml from 'yaml';
import dot from 'dot-object';
import * as semver from 'semver';
import { readFile, writeFile } from 'fs/promises';
import { Flags as flags } from '../commands/flags.js';
import { Templates } from './templates.js';
import * as constants from './constants.js';
import { ConfigManager } from './config_manager.js';
import * as helpers from './helpers.js';
import { getNodeAccountMap } from './helpers.js';
import { SoloLogger } from './logging.js';
import { inject, injectable } from 'tsyringe-neo';
import { patchInject } from './container_helper.js';
import { HEDERA_PLATFORM_VERSION } from '../../version.js';
let ProfileManager = class ProfileManager {
    logger;
    configManager;
    cacheDir;
    profiles;
    profileFile;
    constructor(logger, configManager, cacheDir) {
        this.logger = patchInject(logger, SoloLogger, this.constructor.name);
        this.configManager = patchInject(configManager, ConfigManager, this.constructor.name);
        this.cacheDir = path.resolve(patchInject(cacheDir, 'cacheDir', this.constructor.name));
        this.profiles = new Map();
    }
    /**
     * Load profiles from a profile file and populate the profiles map.
     *
     * @param [forceReload = false] - forces the profiles map to override even if it exists.
     * @returns reference to the populated profiles map.
     *
     * @throws {IllegalArgumentError} if the profile file is not found.
     */
    loadProfiles(forceReload = false) {
        const profileFile = this.configManager.getFlag(flags.profileFile);
        if (!profileFile)
            throw new MissingArgumentError('profileFile is required');
        // return the cached value as quickly as possible
        if (this.profiles && this.profileFile === profileFile && !forceReload) {
            return this.profiles;
        }
        if (!fs.existsSync(profileFile))
            throw new IllegalArgumentError(`profileFile does not exist: ${profileFile}`);
        // load profile file
        this.profiles = new Map();
        const yamlData = fs.readFileSync(profileFile, 'utf8');
        const profileItems = yaml.parse(yamlData);
        // add profiles
        for (const key in profileItems) {
            let profile = profileItems[key];
            profile = profile || {};
            this.profiles.set(key, profile);
        }
        this.profileFile = profileFile;
        return this.profiles;
    }
    /**
     * Get profile from the profiles map, loads them on demand if they are not loaded already.
     *
     * @param profileName - profile name (key in the map).
     * @returns the profile.
     *
     * @throws {IllegalArgumentError} if profiles can't be loaded or the profile name is not found in the map.
     */
    getProfile(profileName) {
        if (!profileName)
            throw new MissingArgumentError('profileName is required');
        if (!this.profiles || this.profiles.size <= 0) {
            this.loadProfiles();
        }
        if (!this.profiles || !this.profiles.has(profileName)) {
            throw new IllegalArgumentError(`Profile does not exists with name: ${profileName}`);
        }
        return this.profiles.get(profileName);
    }
    /**
     * Set value in the YAML object
     * @param itemPath - item path in the yaml
     * @param value - value to be set
     * @param yamlRoot - root of the YAML object
     * @returns
     */
    _setValue(itemPath, value, yamlRoot) {
        // find the location where to set the value in the YAML
        const itemPathParts = itemPath.split('.');
        let parent = yamlRoot;
        let current = parent;
        let prevItemPath = '';
        for (let itemPathPart of itemPathParts) {
            if (helpers.isNumeric(itemPathPart)) {
                // @ts-ignore
                itemPathPart = Number.parseInt(itemPathPart); // numeric path part can only be array index i.e., an integer
                if (!Array.isArray(parent[prevItemPath])) {
                    parent[prevItemPath] = [];
                }
                if (!parent[prevItemPath][itemPathPart]) {
                    parent[prevItemPath][itemPathPart] = {};
                }
                parent = parent[prevItemPath];
                prevItemPath = itemPathPart;
                current = parent[itemPathPart];
            }
            else {
                if (!current[itemPathPart]) {
                    current[itemPathPart] = {};
                }
                parent = current;
                prevItemPath = itemPathPart;
                current = parent[itemPathPart];
            }
        }
        parent[prevItemPath] = value;
        return yamlRoot;
    }
    /**
     * Set items for the chart
     * @param itemPath - item path in the YAML, if empty then root of the YAML object will be used
     * @param items - the element object
     * @param yamlRoot - root of the YAML object to update
     * @private
     */
    _setChartItems(itemPath, items, yamlRoot) {
        if (!items)
            return;
        const dotItems = dot.dot(items);
        for (const key in dotItems) {
            let itemKey = key;
            // if it is an array key like extraEnv[0].JAVA_OPTS, convert it into a dot separated key as extraEnv.0.JAVA_OPTS
            if (key.indexOf('[') !== -1) {
                itemKey = key.replace('[', '.').replace(']', '');
            }
            if (itemPath) {
                this._setValue(`${itemPath}.${itemKey}`, dotItems[key], yamlRoot);
            }
            else {
                this._setValue(itemKey, dotItems[key], yamlRoot);
            }
        }
    }
    resourcesForConsensusPod(profile, nodeAliases, yamlRoot) {
        if (!profile)
            throw new MissingArgumentError('profile is required');
        const accountMap = getNodeAccountMap(nodeAliases);
        // set consensus pod level resources
        for (let nodeIndex = 0; nodeIndex < nodeAliases.length; nodeIndex++) {
            this._setValue(`hedera.nodes.${nodeIndex}.name`, nodeAliases[nodeIndex], yamlRoot);
            this._setValue(`hedera.nodes.${nodeIndex}.nodeId`, `${nodeIndex}`, yamlRoot);
            this._setValue(`hedera.nodes.${nodeIndex}.accountId`, accountMap.get(nodeAliases[nodeIndex]), yamlRoot);
        }
        const stagingDir = Templates.renderStagingDir(this.configManager.getFlag(flags.cacheDir), this.configManager.getFlag(flags.releaseTag));
        if (!fs.existsSync(stagingDir)) {
            fs.mkdirSync(stagingDir, { recursive: true });
        }
        const configTxtPath = this.prepareConfigTxt(this.configManager.getFlag(flags.namespace), accountMap, stagingDir, this.configManager.getFlag(flags.releaseTag), this.configManager.getFlag(flags.app), this.configManager.getFlag(flags.chainId));
        for (const flag of flags.nodeConfigFileFlags.values()) {
            const filePath = this.configManager.getFlag(flag);
            if (!filePath) {
                throw new SoloError(`Configuration file path is missing for: ${flag.name}`);
            }
            const fileName = path.basename(filePath);
            const destPath = path.join(stagingDir, 'templates', fileName);
            this.logger.debug(`Copying configuration file to staging: ${filePath} -> ${destPath}`);
            fs.cpSync(filePath, destPath, { force: true });
        }
        this._setFileContentsAsValue('hedera.configMaps.configTxt', configTxtPath, yamlRoot);
        this._setFileContentsAsValue('hedera.configMaps.log4j2Xml', path.join(stagingDir, 'templates', 'log4j2.xml'), yamlRoot);
        this._setFileContentsAsValue('hedera.configMaps.settingsTxt', path.join(stagingDir, 'templates', 'settings.txt'), yamlRoot);
        this._setFileContentsAsValue('hedera.configMaps.applicationProperties', path.join(stagingDir, 'templates', 'application.properties'), yamlRoot);
        this._setFileContentsAsValue('hedera.configMaps.apiPermissionsProperties', path.join(stagingDir, 'templates', 'api-permission.properties'), yamlRoot);
        this._setFileContentsAsValue('hedera.configMaps.bootstrapProperties', path.join(stagingDir, 'templates', 'bootstrap.properties'), yamlRoot);
        if (this.configManager.getFlag(flags.applicationEnv)) {
            this._setFileContentsAsValue('hedera.configMaps.applicationEnv', this.configManager.getFlag(flags.applicationEnv), yamlRoot);
        }
        if (profile.consensus) {
            // set default for consensus pod
            this._setChartItems('defaults.root', profile.consensus.root, yamlRoot);
            // set sidecar resources
            for (const sidecar of constants.HEDERA_NODE_SIDECARS) {
                this._setChartItems(`defaults.sidecars.${sidecar}`, profile.consensus[sidecar], yamlRoot);
            }
        }
        return yamlRoot;
    }
    resourcesForHaProxyPod(profile, yamlRoot) {
        if (!profile)
            throw new MissingArgumentError('profile is required');
        if (!profile.haproxy)
            return; // use chart defaults
        return this._setChartItems('defaults.haproxy', profile.haproxy, yamlRoot);
    }
    resourcesForEnvoyProxyPod(profile, yamlRoot) {
        if (!profile)
            throw new MissingArgumentError('profile is required');
        if (!profile.envoyProxy)
            return; // use chart defaults
        return this._setChartItems('defaults.envoyProxy', profile.envoyProxy, yamlRoot);
    }
    resourcesForHederaExplorerPod(profile, yamlRoot) {
        if (!profile)
            throw new MissingArgumentError('profile is required');
        if (!profile.explorer)
            return;
        return this._setChartItems('', profile.explorer, yamlRoot);
    }
    resourcesForMinioTenantPod(profile, yamlRoot) {
        if (!profile)
            throw new MissingArgumentError('profile is required');
        // @ts-ignore
        if (!profile.minio || !profile.minio.tenant)
            return; // use chart defaults
        for (const poolIndex in profile.minio.tenant.pools) {
            const pool = profile.minio.tenant.pools[poolIndex];
            for (const prop in pool) {
                if (prop !== 'resources') {
                    this._setValue(`minio-server.tenant.pools.${poolIndex}.${prop}`, pool[prop], yamlRoot);
                }
            }
            this._setChartItems(`minio-server.tenant.pools.${poolIndex}`, pool, yamlRoot);
        }
        return yamlRoot;
    }
    /**
     * Prepare a values file for Solo Helm chart
     * @param profileName - resource profile name
     * @param genesisNetworkData - reference to the constructor
     * @returns return the full path to the values file
     */
    async prepareValuesForSoloChart(profileName) {
        if (!profileName)
            throw new MissingArgumentError('profileName is required');
        const profile = this.getProfile(profileName);
        const nodeAliases = helpers.parseNodeAliases(this.configManager.getFlag(flags.nodeAliasesUnparsed));
        if (!nodeAliases)
            throw new SoloError('Node IDs are not set in the config');
        // generate the YAML
        const yamlRoot = {};
        this.resourcesForConsensusPod(profile, nodeAliases, yamlRoot);
        this.resourcesForHaProxyPod(profile, yamlRoot);
        this.resourcesForEnvoyProxyPod(profile, yamlRoot);
        this.resourcesForMinioTenantPod(profile, yamlRoot);
        const cachedValuesFile = path.join(this.cacheDir, `solo-${profileName}.yaml`);
        return this.writeToYaml(cachedValuesFile, yamlRoot);
    }
    async bumpHederaConfigVersion(applicationPropertiesPath) {
        const lines = (await readFile(applicationPropertiesPath, 'utf-8')).split('\n');
        for (const line of lines) {
            if (line.startsWith('hedera.config.version=')) {
                const version = parseInt(line.split('=')[1]) + 1;
                lines[lines.indexOf(line)] = `hedera.config.version=${version}`;
                break;
            }
        }
        await writeFile(applicationPropertiesPath, lines.join('\n'));
    }
    async prepareValuesForNodeAdd(configTxtPath, applicationPropertiesPath) {
        const yamlRoot = {};
        this._setFileContentsAsValue('hedera.configMaps.configTxt', configTxtPath, yamlRoot);
        await this.bumpHederaConfigVersion(applicationPropertiesPath);
        this._setFileContentsAsValue('hedera.configMaps.applicationProperties', applicationPropertiesPath, yamlRoot);
        const cachedValuesFile = path.join(this.cacheDir, 'solo-node-add.yaml');
        return this.writeToYaml(cachedValuesFile, yamlRoot);
    }
    /**
     * Prepare a values file for rpc-relay Helm chart
     * @param profileName - resource profile name
     * @returns return the full path to the values file
     */
    async prepareValuesForRpcRelayChart(profileName) {
        if (!profileName)
            throw new MissingArgumentError('profileName is required');
        const profile = this.getProfile(profileName);
        if (!profile.rpcRelay)
            return Promise.resolve(); // use chart defaults
        // generate the YAML
        const yamlRoot = {};
        this._setChartItems('', profile.rpcRelay, yamlRoot);
        const cachedValuesFile = path.join(this.cacheDir, `rpcRelay-${profileName}.yaml`);
        return this.writeToYaml(cachedValuesFile, yamlRoot);
    }
    async prepareValuesHederaExplorerChart(profileName) {
        if (!profileName)
            throw new MissingArgumentError('profileName is required');
        const profile = this.getProfile(profileName);
        // generate the YAML
        const yamlRoot = {};
        this.resourcesForHederaExplorerPod(profile, yamlRoot);
        const cachedValuesFile = path.join(this.cacheDir, `explorer-${profileName}.yaml`);
        return this.writeToYaml(cachedValuesFile, yamlRoot);
    }
    /**
     * Writes the YAML to file.
     *
     * @param cachedValuesFile - the target file to write the YAML root to.
     * @param yamlRoot - object to turn into YAML and write to file.
     */
    async writeToYaml(cachedValuesFile, yamlRoot) {
        return await new Promise((resolve, reject) => {
            fs.writeFile(cachedValuesFile, yaml.stringify(yamlRoot), err => {
                if (err) {
                    reject(err);
                }
                resolve(cachedValuesFile);
            });
        });
    }
    /**
     * Prepare a values file for mirror-node Helm chart
     * @param profileName - resource profile name
     * @returns the full path to the values file
     */
    async prepareValuesForMirrorNodeChart(profileName) {
        if (!profileName)
            throw new MissingArgumentError('profileName is required');
        const profile = this.getProfile(profileName);
        if (!profile.mirror)
            return Promise.resolve(); // use chart defaults
        // generate the YAML
        const yamlRoot = {};
        if (profile.mirror.postgresql) {
            if (profile.mirror.postgresql.persistence) {
                this._setValue('postgresql.persistence.size', profile.mirror.postgresql.persistence.size, yamlRoot);
            }
            this._setChartItems('postgresql.postgresql', profile.mirror.postgresql.postgresql, yamlRoot);
        }
        this._setChartItems('importer', profile.mirror.importer, yamlRoot);
        this._setChartItems('rest', profile.mirror.rest, yamlRoot);
        this._setChartItems('web3', profile.mirror.web3, yamlRoot);
        this._setChartItems('grpc', profile.mirror.grpc, yamlRoot);
        this._setChartItems('monitor', profile.mirror.monitor, yamlRoot);
        const cachedValuesFile = path.join(this.cacheDir, `mirror-${profileName}.yaml`);
        return this.writeToYaml(cachedValuesFile, yamlRoot);
    }
    /**
     * Writes the contents of a file as a value for the given nested item path in the YAML object
     * @param itemPath - nested item path in the YAML object to store the file contents
     * @param valueFilePath - path to the file whose contents will be stored in the YAML object
     * @param yamlRoot - root of the YAML object
     */
    _setFileContentsAsValue(itemPath, valueFilePath, yamlRoot) {
        const fileContents = fs.readFileSync(valueFilePath, 'utf8');
        this._setValue(itemPath, fileContents, yamlRoot);
    }
    /**
     * Prepares config.txt file for the node
     * @param namespace - namespace where the network is deployed
     * @param nodeAccountMap - the map of node aliases to account IDs
     * @param destPath - path to the destination directory to write the config.txt file
     * @param releaseTagOverride - release tag override
     * @param [appName] - the app name (default: HederaNode.jar)
     * @param [chainId] - chain ID (298 for local network)
     * @param genesisNetworkData
     * @returns the config.txt file path
     */
    prepareConfigTxt(namespace, nodeAccountMap, destPath, releaseTagOverride, appName = constants.HEDERA_APP_NAME, chainId = constants.HEDERA_CHAIN_ID) {
        let releaseTag = releaseTagOverride;
        if (!nodeAccountMap || nodeAccountMap.size === 0) {
            throw new MissingArgumentError('nodeAccountMap the map of node IDs to account IDs is required');
        }
        if (!releaseTag)
            releaseTag = HEDERA_PLATFORM_VERSION;
        if (!fs.existsSync(destPath)) {
            throw new IllegalArgumentError(`config destPath does not exist: ${destPath}`, destPath);
        }
        // init variables
        const internalPort = +constants.HEDERA_NODE_INTERNAL_GOSSIP_PORT;
        const externalPort = +constants.HEDERA_NODE_EXTERNAL_GOSSIP_PORT;
        const nodeStakeAmount = constants.HEDERA_NODE_DEFAULT_STAKE_AMOUNT;
        // @ts-expect-error - TS2353: Object literal may only specify known properties, and includePrerelease does not exist in type Options
        const releaseVersion = semver.parse(releaseTag, { includePrerelease: true });
        try {
            const configLines = [];
            configLines.push(`swirld, ${chainId}`);
            configLines.push(`app, ${appName}`);
            let nodeSeq = 0;
            for (const nodeAlias of nodeAccountMap.keys()) {
                const internalIP = Templates.renderFullyQualifiedNetworkPodName(namespace, nodeAlias);
                const externalIP = Templates.renderFullyQualifiedNetworkSvcName(namespace, nodeAlias);
                const account = nodeAccountMap.get(nodeAlias);
                configLines.push(`address, ${nodeSeq}, ${nodeSeq}, ${nodeAlias}, ${nodeStakeAmount}, ${internalIP}, ${internalPort}, ${externalIP}, ${externalPort}, ${account}`);
                nodeSeq += 1;
            }
            // TODO: remove once we no longer need less than v0.56
            if (releaseVersion.minor >= 41 && releaseVersion.minor < 56) {
                configLines.push(`nextNodeId, ${nodeSeq}`);
            }
            const configFilePath = path.join(destPath, 'config.txt');
            fs.writeFileSync(configFilePath, configLines.join('\n'));
            return configFilePath;
        }
        catch (e) {
            throw new SoloError('failed to generate config.txt', e);
        }
    }
};
ProfileManager = __decorate([
    injectable(),
    __param(0, inject(SoloLogger)),
    __param(1, inject(ConfigManager)),
    __param(2, inject('cacheDir')),
    __metadata("design:paramtypes", [SoloLogger,
        ConfigManager, String])
], ProfileManager);
export { ProfileManager };
//# sourceMappingURL=profile_manager.js.map