// SPDX-License-Identifier: Apache-2.0
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); }
};
var RemoteConfigRuntimeState_1;
import { inject, injectable } from 'tsyringe-neo';
import { ReadRemoteConfigBeforeLoadError } from '../../../errors/read-remote-config-before-load-error.js';
import { WriteRemoteConfigBeforeLoadError } from '../../../errors/write-remote-config-before-load-error.js';
import { RemoteConfigSource } from '../../../../data/configuration/impl/remote-config-source.js';
import { YamlConfigMapStorageBackend } from '../../../../data/backend/impl/yaml-config-map-storage-backend.js';
import { SemVer } from 'semver';
import { InjectTokens } from '../../../../core/dependency-injection/inject-tokens.js';
import { patchInject } from '../../../../core/dependency-injection/container-helper.js';
import { NamespaceName } from '../../../../types/namespace/namespace-name.js';
import { ComponentStateMetadataSchema } from '../../../../data/schema/model/remote/state/component-state-metadata-schema.js';
import { Templates } from '../../../../core/templates.js';
import { DeploymentPhase } from '../../../../data/schema/model/remote/deployment-phase.js';
import { getSoloVersion } from '../../../../../version.js';
import * as constants from '../../../../core/constants.js';
import { SoloError } from '../../../../core/errors/solo-error.js';
import { Flags as flags } from '../../../../commands/flags.js';
import { promptTheUserForDeployment } from '../../../../core/resolvers.js';
import { ConsensusNode } from '../../../../core/model/consensus-node.js';
import { ComponentTypes } from '../../../../core/config/remote/enumerations/component-types.js';
import { LocalConfigRuntimeState } from '../local/local-config-runtime-state.js';
import { RemoteConfigMetadataSchema } from '../../../../data/schema/model/remote/remote-config-metadata-schema.js';
import { ApplicationVersionsSchema } from '../../../../data/schema/model/common/application-versions-schema.js';
import { ClusterSchema } from '../../../../data/schema/model/common/cluster-schema.js';
import { DeploymentStateSchema } from '../../../../data/schema/model/remote/deployment-state-schema.js';
import { DeploymentHistorySchema } from '../../../../data/schema/model/remote/deployment-history-schema.js';
import { RemoteConfigSchemaDefinition } from '../../../../data/schema/migration/impl/remote/remote-config-schema-definition.js';
import { RemoteConfigSchema } from '../../../../data/schema/model/remote/remote-config-schema.js';
import { ConsensusNodeStateSchema } from '../../../../data/schema/model/remote/state/consensus-node-state-schema.js';
import { RemoteConfig } from './remote-config.js';
import * as helpers from '../../../../core/helpers.js';
var RuntimeStatePhase;
(function (RuntimeStatePhase) {
    RuntimeStatePhase["Loaded"] = "loaded";
    RuntimeStatePhase["NotLoaded"] = "not_loaded";
})(RuntimeStatePhase || (RuntimeStatePhase = {}));
let RemoteConfigRuntimeState = class RemoteConfigRuntimeState {
    static { RemoteConfigRuntimeState_1 = this; }
    k8Factory;
    logger;
    localConfig;
    configManager;
    remoteConfigValidator;
    objectMapper;
    static SOLO_REMOTE_CONFIGMAP_DATA_KEY = 'remote-config-data';
    phase = RuntimeStatePhase.NotLoaded;
    clusterReferences = new Map();
    namespace;
    source;
    backend;
    _remoteConfig;
    constructor(k8Factory, logger, localConfig, configManager, remoteConfigValidator, objectMapper) {
        this.k8Factory = k8Factory;
        this.logger = logger;
        this.localConfig = localConfig;
        this.configManager = configManager;
        this.remoteConfigValidator = remoteConfigValidator;
        this.objectMapper = objectMapper;
        this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name);
        this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name);
        this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name);
        this.configManager = patchInject(configManager, InjectTokens.ConfigManager, this.constructor.name);
        this.remoteConfigValidator = patchInject(remoteConfigValidator, InjectTokens.RemoteConfigValidator, this.constructor.name);
        this.objectMapper = patchInject(objectMapper, InjectTokens.ObjectMapper, this.constructor.name);
    }
    get configuration() {
        this.failIfNotLoaded();
        return this._remoteConfig;
    }
    get components() {
        this.failIfNotLoaded();
        return this._remoteConfig.components;
    }
    get currentCluster() {
        return this.k8Factory.default().clusters().readCurrent();
    }
    async load(namespace, context) {
        if (this.isLoaded()) {
            return;
        }
        await this.populateFromExisting(namespace, context);
    }
    async populateFromConfigMap(configMap, remoteConfig) {
        this.backend = new YamlConfigMapStorageBackend(configMap);
        this.source = new RemoteConfigSource(new RemoteConfigSchemaDefinition(this.objectMapper), this.objectMapper, this.backend);
        await this.source.load();
        if (remoteConfig) {
            this.source.setModelData(remoteConfig);
        }
        this._remoteConfig = new RemoteConfig(this.source.modelData);
        this.phase = RuntimeStatePhase.Loaded;
    }
    async updateConfigMap(context, namespace, data) {
        await this.k8Factory.getK8(context).configMaps().update(namespace, constants.SOLO_REMOTE_CONFIGMAP_NAME, data);
    }
    isLoaded() {
        return this.phase === RuntimeStatePhase.Loaded;
    }
    failIfNotLoaded() {
        if (!this.isLoaded()) {
            throw new ReadRemoteConfigBeforeLoadError('Attempting to read from remote config before loading it');
        }
    }
    async persist() {
        if (!this.isLoaded()) {
            throw new WriteRemoteConfigBeforeLoadError('Attempting to persist remote config before loading it');
        }
        await this.source.persist();
        const remoteConfigDataBytes = await this.backend.readBytes(RemoteConfigRuntimeState_1.SOLO_REMOTE_CONFIGMAP_DATA_KEY);
        const remoteConfigData = {
            [RemoteConfigRuntimeState_1.SOLO_REMOTE_CONFIGMAP_DATA_KEY]: remoteConfigDataBytes.toString('utf8'),
        };
        const promises = [];
        for (const context of this.clusterReferences.keys()) {
            promises.push(this.updateConfigMap(context, this.namespace, remoteConfigData));
        }
        await Promise.all(promises);
    }
    async create(argv, ledgerPhase, nodeAliases, namespace, deploymentName, clusterReference, context, dnsBaseDomain, dnsConsensusNodePattern) {
        this.populateClusterReferences(deploymentName);
        const consensusNodeStates = nodeAliases.map((nodeAlias) => {
            return new ConsensusNodeStateSchema(new ComponentStateMetadataSchema(Templates.nodeIdFromNodeAlias(nodeAlias), namespace.name, clusterReference, DeploymentPhase.REQUESTED));
        });
        const userIdentity = this.localConfig.configuration.userIdentity;
        const cliVersion = new SemVer(getSoloVersion());
        const command = argv._.join(' ');
        const cluster = new ClusterSchema(clusterReference, namespace.name, deploymentName, dnsBaseDomain, dnsConsensusNodePattern);
        const remoteConfig = new RemoteConfigSchema(1, new RemoteConfigMetadataSchema(new Date(), userIdentity), new ApplicationVersionsSchema(cliVersion), [cluster], new DeploymentStateSchema(ledgerPhase, consensusNodeStates), new DeploymentHistorySchema([command], command));
        const configMap = await this.createConfigMap(namespace, context);
        await this.populateFromConfigMap(configMap, remoteConfig);
        await this.persist();
    }
    async createFromExisting(namespace, clusterReference, deploymentName, componentFactory, dnsBaseDomain, dnsConsensusNodePattern, existingClusterContext, argv, nodeAliases) {
        await this.populateFromExisting(namespace, existingClusterContext);
        this.populateClusterReferences(deploymentName);
        const newClusterContext = this.localConfig.configuration.clusterRefs
            .get(clusterReference.toString())
            ?.toString();
        //? Create copy of the existing remote config inside the new cluster
        await this.createConfigMap(namespace, newClusterContext);
        await this.persist();
        //* update the command history
        this.addCommandToHistory(argv._.join(' '));
        //* add the new clusters
        this.configuration.addCluster(new ClusterSchema(clusterReference, namespace.name, deploymentName, dnsBaseDomain, dnsConsensusNodePattern));
        //* add the new nodes to components
        for (const nodeAlias of nodeAliases) {
            this.configuration.components.addNewComponent(componentFactory.createNewConsensusNodeComponent(Templates.nodeIdFromNodeAlias(nodeAlias), clusterReference, namespace, DeploymentPhase.REQUESTED), ComponentTypes.ConsensusNode);
        }
        await this.persist();
    }
    addCommandToHistory(command) {
        this.source.modelData.history.commands.push(command);
        this.source.modelData.history.lastExecutedCommand = command;
        if (this.source.modelData.history.commands.length > constants.SOLO_REMOTE_CONFIG_MAX_COMMAND_IN_HISTORY) {
            this.source.modelData.history.commands.shift();
        }
    }
    async createConfigMap(namespace, context) {
        const name = constants.SOLO_REMOTE_CONFIGMAP_NAME;
        const labels = constants.SOLO_REMOTE_CONFIGMAP_LABELS;
        await this.k8Factory
            .getK8(context)
            .configMaps()
            .create(namespace, name, labels, { [RemoteConfigRuntimeState_1.SOLO_REMOTE_CONFIGMAP_DATA_KEY]: '{}' });
        return await this.k8Factory.getK8(context).configMaps().read(namespace, name);
    }
    async getConfigMap(namespace, context) {
        const configMap = await this.k8Factory
            .getK8(context)
            .configMaps()
            .read(namespace, constants.SOLO_REMOTE_CONFIGMAP_NAME);
        if (!configMap) {
            throw new SoloError(`Remote config ConfigMap not found for namespace: ${namespace}, context: ${context}`);
        }
        return configMap;
    }
    async populateFromExisting(namespace, context) {
        const remoteConfigConfigMap = await this.getConfigMap(namespace, context);
        await this.populateFromConfigMap(remoteConfigConfigMap);
    }
    async remoteConfigExists(namespace, context) {
        const configMap = await this.getConfigMap(namespace, context);
        return !!configMap;
    }
    populateClusterReferences(deploymentName) {
        const deployment = this.localConfig.configuration.deploymentByName(deploymentName);
        this.namespace = NamespaceName.of(deployment.namespace);
        for (const clusterReference of deployment.clusters) {
            const context = this.localConfig.configuration.clusterRefs.get(clusterReference.toString())?.toString();
            this.clusterReferences.set(context, clusterReference.toString());
        }
        return this.localConfig.configuration.clusterRefs.get(deployment.clusters.get(0)?.toString())?.toString();
    }
    /**
     * Performs the loading of the remote configuration.
     * Checks if the configuration is already loaded, otherwise loads and adds the command to history.
     *
     * @param argv - arguments containing command input for historical reference.
     * @param validate - whether to validate the remote configuration.
     * @param [skipConsensusNodesValidation] - whether or not to validate the consensusNodes
     */
    async loadAndValidate(argv, validate = true, skipConsensusNodesValidation = true) {
        await this.setDefaultNamespaceAndDeploymentIfNotSet(argv);
        this.setDefaultContextIfNotSet();
        const deploymentName = this.configManager.getFlag(flags.deployment);
        const context = this.populateClusterReferences(deploymentName);
        // TODO: Compare configs from clusterReferences
        await this.load(this.namespace, context);
        this.logger.info('Remote config loaded');
        if (!validate) {
            return;
        }
        await this.remoteConfigValidator.validateComponents(this.namespace, skipConsensusNodesValidation, this.configuration.state);
        const currentCommand = argv._?.join(' ');
        const commandArguments = flags.stringifyArgv(argv);
        this.addCommandToHistory(`Executed by ${this.localConfig.configuration.userIdentity.name}: ${currentCommand} ${commandArguments}`.trim());
        this.populateVersionsInMetadata(argv, this.source.modelData);
        await this.persist();
    }
    populateVersionsInMetadata(argv, remoteConfig) {
        const command = argv._[0];
        const subcommand = argv._[1];
        const isCommandUsingSoloChartVersionFlag = (command === 'network' && subcommand === 'deploy') ||
            (command === 'network' && subcommand === 'refresh') ||
            (command === 'node' && subcommand === 'update') ||
            (command === 'node' && subcommand === 'update-execute') ||
            (command === 'node' && subcommand === 'add') ||
            (command === 'node' && subcommand === 'add-execute') ||
            (command === 'node' && subcommand === 'delete') ||
            (command === 'node' && subcommand === 'delete-execute');
        // TBD how to update remoteConfig.versions.cli
        if (argv[flags.soloChartVersion.name]) {
            remoteConfig.versions.chart = new SemVer(argv[flags.soloChartVersion.name]);
        }
        else if (isCommandUsingSoloChartVersionFlag) {
            remoteConfig.versions.chart = new SemVer(flags.soloChartVersion.definition.defaultValue);
        }
        const isCommandUsingReleaseTagVersionFlag = (command === 'node' && subcommand !== 'keys' && subcommand !== 'logs' && subcommand !== 'states') ||
            (command === 'network' && subcommand === 'deploy');
        if (argv[flags.releaseTag.name]) {
            remoteConfig.versions.consensusNode = new SemVer(argv[flags.releaseTag.name]);
        }
        else if (isCommandUsingReleaseTagVersionFlag) {
            remoteConfig.versions.consensusNode = new SemVer(flags.releaseTag.definition.defaultValue);
        }
        if (argv[flags.mirrorNodeVersion.name]) {
            remoteConfig.versions.mirrorNodeChart = new SemVer(argv[flags.mirrorNodeVersion.name]);
        }
        else if (command === 'mirror-node' && subcommand === 'deploy') {
            remoteConfig.versions.mirrorNodeChart = new SemVer(flags.mirrorNodeVersion.definition.defaultValue);
        }
        if (argv[flags.explorerVersion.name]) {
            remoteConfig.versions.explorerChart = new SemVer(argv[flags.explorerVersion.name]);
        }
        else if (command === 'explorer' && subcommand === 'deploy') {
            remoteConfig.versions.explorerChart = new SemVer(flags.explorerVersion.definition.defaultValue);
        }
        if (argv[flags.relayReleaseTag.name]) {
            remoteConfig.versions.jsonRpcRelayChart = new SemVer(argv[flags.relayReleaseTag.name]);
        }
        else if (command === 'relay' && subcommand === 'deploy') {
            remoteConfig.versions.jsonRpcRelayChart = new SemVer(flags.relayReleaseTag.definition.defaultValue);
        }
    }
    async deleteComponents() {
        this._remoteConfig.state.consensusNodes = [];
        this._remoteConfig.state.blockNodes = [];
        this._remoteConfig.state.envoyProxies = [];
        this._remoteConfig.state.haProxies = [];
        this._remoteConfig.state.explorers = [];
        this._remoteConfig.state.mirrorNodes = [];
        this._remoteConfig.state.relayNodes = [];
    }
    async setDefaultNamespaceAndDeploymentIfNotSet(argv) {
        if (this.configManager.hasFlag(flags.namespace)) {
            return;
        }
        // TODO: Current quick fix for commands where namespace is not passed
        let deploymentName = this.configManager.getFlag(flags.deployment);
        let currentDeployment = this.localConfig.configuration.deploymentByName(deploymentName);
        if (!deploymentName) {
            deploymentName = await promptTheUserForDeployment(this.configManager);
            currentDeployment = this.localConfig.configuration.deploymentByName(deploymentName);
            // TODO: Fix once we have the DataManager,
            //       without this the user will be prompted a second time for the deployment
            // TODO: we should not be mutating argv
            argv[flags.deployment.name] = deploymentName;
            this.logger.warn(`Deployment name not found in flags or local config, setting it in argv and config manager to: ${deploymentName}`);
            this.configManager.setFlag(flags.deployment, deploymentName);
        }
        if (!currentDeployment) {
            throw new SoloError(`Selected deployment name is not set in local config - ${deploymentName}`);
        }
        const namespace = currentDeployment.namespace;
        this.logger.warn(`Namespace not found in flags, setting it to: ${namespace}`);
        this.configManager.setFlag(flags.namespace, namespace);
        argv[flags.namespace.name] = namespace;
    }
    setDefaultContextIfNotSet() {
        if (this.configManager.hasFlag(flags.context)) {
            return;
        }
        const context = this.getContextForFirstCluster() ?? this.k8Factory.default().contexts().readCurrent();
        if (!context) {
            throw new SoloError("Context is not passed and default one can't be acquired");
        }
        this.logger.warn(`Context not found in flags, setting it to: ${context}`);
        this.configManager.setFlag(flags.context, context);
    }
    //* Common Commands
    /**
     * Get the consensus nodes from the remoteConfig and use the localConfig to get the context
     * @returns an array of ConsensusNode objects
     */
    getConsensusNodes() {
        if (!this.isLoaded()) {
            throw new SoloError('Remote configuration is not loaded, and was expected to be loaded');
        }
        const consensusNodes = [];
        let cluster;
        let context;
        let nodeAlias;
        for (const node of Object.values(this.configuration.state.consensusNodes)) {
            try {
                cluster = this.configuration.clusters.find((cluster) => cluster.name === node.metadata.cluster);
                context = this.localConfig.configuration.clusterRefs.get(node.metadata.cluster)?.toString();
                nodeAlias = Templates.renderNodeAliasFromNumber(node.metadata.id + 1);
                consensusNodes.push(new ConsensusNode(nodeAlias, node.metadata.id, node.metadata.namespace, node.metadata.cluster, context, cluster.dnsBaseDomain, cluster.dnsConsensusNodePattern, Templates.renderConsensusNodeFullyQualifiedDomainName(nodeAlias, node.metadata.id, node.metadata.namespace, node.metadata.cluster, cluster.dnsBaseDomain, cluster.dnsConsensusNodePattern)));
            }
            catch (error) {
                throw new SoloError(`Error getting consensus nodes: ${error.message}, for: [context: ${context}], [nodeAlias: ${nodeAlias}], [node: ${JSON.stringify(node)}], [cluster: ${JSON.stringify(cluster)}]`, error);
            }
        }
        // return the consensus nodes
        return consensusNodes;
    }
    /**
     * Gets a list of distinct contexts from the consensus nodes.
     * @returns an array of context strings.
     */
    getContexts() {
        return [...new Set(this.getConsensusNodes().map((node) => node.context))];
    }
    /**
     * Gets a list of distinct cluster references from the consensus nodes.
     * @returns an object of cluster references.
     */
    getClusterRefs() {
        const nodes = this.getConsensusNodes();
        const accumulator = new Map();
        for (const node of nodes) {
            accumulator.set(node.cluster, node.context);
        }
        return accumulator;
    }
    getContextForFirstCluster() {
        const deploymentName = this.configManager.getFlag(flags.deployment);
        const clusterReference = this.localConfig.configuration.deploymentByName(deploymentName).clusters[0];
        const context = this.localConfig.configuration.clusterRefs.get(clusterReference)?.toString();
        this.logger.debug(`Using context ${context} for cluster ${clusterReference} for deployment ${deploymentName}`);
        return context;
    }
    getNamespace() {
        return NamespaceName.of(this.configuration.clusters?.at(0)?.namespace);
    }
    extractContextFromConsensusNodes(nodeAlias) {
        return helpers.extractContextFromConsensusNodes(nodeAlias, this.getConsensusNodes());
    }
};
RemoteConfigRuntimeState = RemoteConfigRuntimeState_1 = __decorate([
    injectable(),
    __param(0, inject(InjectTokens.K8Factory)),
    __param(1, inject(InjectTokens.SoloLogger)),
    __param(2, inject(InjectTokens.LocalConfigRuntimeState)),
    __param(3, inject(InjectTokens.ConfigManager)),
    __param(4, inject(InjectTokens.RemoteConfigValidator)),
    __param(5, inject(InjectTokens.ObjectMapper)),
    __metadata("design:paramtypes", [Object, Object, LocalConfigRuntimeState, Function, Object, Object])
], RemoteConfigRuntimeState);
export { RemoteConfigRuntimeState };
//# sourceMappingURL=remote-config-runtime-state.js.map