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 * as constants from '../../constants.js';
import { MissingArgumentError, SoloError } from '../../errors.js';
import { RemoteConfigDataWrapper } from './remote_config_data_wrapper.js';
import chalk from 'chalk';
import { RemoteConfigMetadata } from './metadata.js';
import { Flags as flags } from '../../../commands/flags.js';
import * as yaml from 'yaml';
import { ComponentsDataWrapper } from './components_data_wrapper.js';
import { RemoteConfigValidator } from './remote_config_validator.js';
import { K8 } from '../../k8.js';
import { SoloLogger } from '../../logging.js';
import { ConfigManager } from '../../config_manager.js';
import { LocalConfig } from '../local_config.js';
import { StatusCodes } from 'http-status-codes';
import { inject, injectable } from 'tsyringe-neo';
import { patchInject } from '../../container_helper.js';
/**
 * Uses Kubernetes ConfigMaps to manage the remote configuration data by creating, loading, modifying,
 * and saving the configuration data to and from a Kubernetes cluster.
 */
let RemoteConfigManager = class RemoteConfigManager {
    k8;
    logger;
    localConfig;
    configManager;
    /** Stores the loaded remote configuration data. */
    remoteConfig;
    /**
     * @param k8 - The Kubernetes client used for interacting with ConfigMaps.
     * @param logger - The logger for recording activity and errors.
     * @param localConfig - Local configuration for the remote config.
     * @param configManager - Manager to retrieve application flags and settings.
     */
    constructor(k8, logger, localConfig, configManager) {
        this.k8 = k8;
        this.logger = logger;
        this.localConfig = localConfig;
        this.configManager = configManager;
        this.k8 = patchInject(k8, K8, this.constructor.name);
        this.logger = patchInject(logger, SoloLogger, this.constructor.name);
        this.localConfig = patchInject(localConfig, LocalConfig, this.constructor.name);
        this.configManager = patchInject(configManager, ConfigManager, this.constructor.name);
    }
    /* ---------- Getters ---------- */
    get currentCluster() {
        return this.localConfig.currentDeploymentName;
    }
    /** @returns the components data wrapper cloned */
    get components() {
        return this.remoteConfig.components.clone();
    }
    /* ---------- Readers and Modifiers ---------- */
    /**
     * Modifies the loaded remote configuration data using a provided callback function.
     * The callback operates on the configuration data, which is then saved to the cluster.
     *
     * @param callback - an async function that modifies the remote configuration data.
     * @throws {@link SoloError} if the configuration is not loaded before modification.
     */
    async modify(callback) {
        if (!this.remoteConfig) {
            return;
            // TODO see if this should be disabled to make it an optional feature
            // throw new SoloError('Attempting to modify remote config without loading it first')
        }
        await callback(this.remoteConfig);
        await this.save();
    }
    /**
     * Creates a new remote configuration in the Kubernetes cluster.
     * Gathers data from the local configuration and constructs a new ConfigMap
     * entry in the cluster with initial command history and metadata.
     */
    async create() {
        const clusters = {};
        Object.entries(this.localConfig.deployments).forEach(([namespace, deployment]) => {
            deployment.clusters.forEach(cluster => (clusters[cluster] = namespace));
        });
        this.remoteConfig = new RemoteConfigDataWrapper({
            metadata: new RemoteConfigMetadata(this.getNamespace(), new Date(), this.localConfig.userEmailAddress),
            clusters,
            components: ComponentsDataWrapper.initializeEmpty(),
            lastExecutedCommand: 'deployment create',
            commandHistory: ['deployment create'],
        });
        await this.createConfigMap();
    }
    /**
     * Saves the currently loaded remote configuration data to the Kubernetes cluster.
     * @throws {@link SoloError} if there is no remote configuration data to save.
     */
    async save() {
        if (!this.remoteConfig) {
            throw new SoloError('Attempted to save remote config without data');
        }
        await this.replaceConfigMap();
    }
    /**
     * Loads the remote configuration from the Kubernetes cluster if it exists.
     * @returns true if the configuration is loaded successfully.
     */
    async load() {
        if (this.remoteConfig)
            return true;
        const configMap = await this.getConfigMap();
        if (!configMap)
            return false;
        this.remoteConfig = RemoteConfigDataWrapper.fromConfigmap(configMap);
        return true;
    }
    /* ---------- Listr Task Builders ---------- */
    /**
     * Builds a task for loading the remote configuration, intended for use with Listr task management.
     * Checks if the configuration is already loaded, otherwise loads and adds the command to history.
     *
     * @param argv - arguments containing command input for historical reference.
     * @returns a Listr task which loads the remote configuration.
     */
    buildLoadTask(argv) {
        const self = this;
        return {
            title: 'Load remote config',
            task: async (_, task) => {
                try {
                    self.setDefaultNamespaceIfNotSet();
                    self.setDefaultContextIfNotSet();
                }
                catch {
                    return; // TODO
                }
                if (!(await self.load())) {
                    task.title = `${task.title} - ${chalk.red('remote config not found')}`;
                    // TODO see if this should be disabled to make it an optional feature
                    return;
                    // throw new SoloError('Failed to load remote config')
                }
                await RemoteConfigValidator.validateComponents(self.remoteConfig.components, self.k8);
                const currentCommand = argv._.join(' ');
                self.remoteConfig.addCommandToHistory(currentCommand);
                await self.save();
            },
        };
    }
    /**
     * Builds a task for creating a new remote configuration, intended for use with Listr task management.
     * Merges cluster mappings from the provided context into the local configuration, then creates the remote config.
     *
     * @returns a Listr task which creates the remote configuration.
     */
    buildCreateTask() {
        const self = this;
        return {
            title: 'Create remote config',
            task: async (_, task) => {
                const localConfigExists = this.localConfig.configFileExists();
                if (!localConfigExists) {
                    throw new SoloError("Local config doesn't exist");
                }
                if (await self.load()) {
                    task.title = `${task.title} - ${chalk.red('Remote config already exists')}}`;
                    throw new SoloError('Remote config already exists');
                }
                await self.create();
            },
        };
    }
    /* ---------- Utilities ---------- */
    isLoaded() {
        return !!this.remoteConfig;
    }
    /**
     * Retrieves the ConfigMap containing the remote configuration from the Kubernetes cluster.
     *
     * @returns the remote configuration data.
     * @throws {@link SoloError} if the ConfigMap could not be read and the error is not a 404 status.
     */
    async getConfigMap() {
        try {
            return await this.k8.getNamespacedConfigMap(constants.SOLO_REMOTE_CONFIGMAP_NAME);
        }
        catch (error) {
            if (error.meta?.statusCode !== StatusCodes.NOT_FOUND) {
                throw new SoloError('Failed to read remote config from cluster', error);
            }
            return null;
        }
    }
    /**
     * Creates a new ConfigMap entry in the Kubernetes cluster with the remote configuration data.
     */
    async createConfigMap() {
        await this.k8.createNamespacedConfigMap(constants.SOLO_REMOTE_CONFIGMAP_NAME, constants.SOLO_REMOTE_CONFIGMAP_LABELS, { 'remote-config-data': yaml.stringify(this.remoteConfig.toObject()) });
    }
    /** Replaces an existing ConfigMap in the Kubernetes cluster with the current remote configuration data. */
    async replaceConfigMap() {
        await this.k8.replaceNamespacedConfigMap(constants.SOLO_REMOTE_CONFIGMAP_NAME, constants.SOLO_REMOTE_CONFIGMAP_LABELS, { 'remote-config-data': yaml.stringify(this.remoteConfig.toObject()) });
    }
    setDefaultNamespaceIfNotSet() {
        if (this.configManager.hasFlag(flags.namespace))
            return;
        if (!this.localConfig?.currentDeploymentName) {
            this.logger.error('Current deployment name is not set in local config', this.localConfig);
            throw new SoloError('Current deployment name is not set in local config');
        }
        // TODO: Current quick fix for commands where namespace is not passed
        const namespace = this.localConfig.currentDeploymentName.replace(/^kind-/, '');
        this.configManager.setFlag(flags.namespace, namespace);
    }
    setDefaultContextIfNotSet() {
        if (this.configManager.hasFlag(flags.context))
            return;
        const context = this.k8.getKubeConfig().currentContext;
        if (!context) {
            this.logger.error("Context is not passed and default one can't be acquired", this.localConfig);
            throw new SoloError("Context is not passed and default one can't be acquired");
        }
        this.configManager.setFlag(flags.context, context);
    }
    // cluster will be retrieved from LocalConfig based the context to cluster mapping
    /**
     * Retrieves the namespace value from the configuration manager's flags.
     * @returns string - The namespace value if set.
     */
    getNamespace() {
        const ns = this.configManager.getFlag(flags.namespace);
        if (!ns)
            throw new MissingArgumentError('namespace is not set');
        return ns;
    }
};
RemoteConfigManager = __decorate([
    injectable(),
    __param(0, inject(K8)),
    __param(1, inject(SoloLogger)),
    __param(2, inject(LocalConfig)),
    __param(3, inject(ConfigManager)),
    __metadata("design:paramtypes", [K8,
        SoloLogger,
        LocalConfig,
        ConfigManager])
], RemoteConfigManager);
export { RemoteConfigManager };
//# sourceMappingURL=remote_config_manager.js.map