// 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 DeploymentCommand_1;
import { Listr } from 'listr2';
import { SoloError } from '../core/errors/solo-error.js';
import { BaseCommand } from './base.js';
import { Flags as flags } from './flags.js';
import * as constants from '../core/constants.js';
import chalk from 'chalk';
import { ErrorMessages } from '../core/error-messages.js';
import { NamespaceName } from '../types/namespace/namespace-name.js';
import { container, inject, injectable } from 'tsyringe-neo';
import { InjectTokens } from '../core/dependency-injection/inject-tokens.js';
import { Templates } from '../core/templates.js';
import { resolveNamespaceFromDeployment } from '../core/resolvers.js';
import { patchInject } from '../core/dependency-injection/container-helper.js';
import { DeploymentStates } from '../core/config/remote/enumerations/deployment-states.js';
import { LedgerPhase } from '../data/schema/model/remote/ledger-phase.js';
import { StringFacade } from '../business/runtime-state/facade/string-facade.js';
let DeploymentCommand = class DeploymentCommand extends BaseCommand {
    static { DeploymentCommand_1 = this; }
    tasks;
    componentFactory;
    static CREATE_COMMAND = 'deployment create';
    static ADD_COMMAND = 'deployment add-cluster';
    constructor(tasks, componentFactory) {
        super();
        this.tasks = tasks;
        this.componentFactory = componentFactory;
        this.tasks = patchInject(tasks, InjectTokens.ClusterCommandTasks, this.constructor.name);
    }
    static COMMAND_NAME = 'deployment';
    static CREATE_FLAGS_LIST = {
        required: [flags.namespace, flags.deployment],
        optional: [flags.quiet, flags.realm, flags.shard],
    };
    static DELETE_FLAGS_LIST = {
        required: [flags.deployment],
        optional: [flags.quiet],
    };
    static ADD_CLUSTER_FLAGS_LIST = {
        required: [flags.deployment, flags.clusterRef],
        optional: [
            flags.quiet,
            flags.enableCertManager,
            flags.numberOfConsensusNodes,
            flags.dnsBaseDomain,
            flags.dnsConsensusNodePattern,
        ],
    };
    static LIST_DEPLOYMENTS_FLAGS_LIST = {
        required: [flags.clusterRef],
        optional: [flags.quiet],
    };
    /**
     * Create new deployment inside the local config
     */
    async create(argv) {
        const self = this;
        const tasks = this.taskList.newTaskList([
            {
                title: 'Initialize',
                task: async (context_, task) => {
                    await self.localConfig.load();
                    self.configManager.update(argv);
                    await self.configManager.executePrompt(task, [flags.namespace, flags.deployment]);
                    context_.config = {
                        quiet: self.configManager.getFlag(flags.quiet),
                        namespace: self.configManager.getFlag(flags.namespace),
                        deployment: self.configManager.getFlag(flags.deployment),
                        realm: self.configManager.getFlag(flags.realm) || flags.realm.definition.defaultValue,
                        shard: self.configManager.getFlag(flags.shard) || flags.shard.definition.defaultValue,
                    };
                    if (self.localConfig.configuration.deployments &&
                        self.localConfig.configuration.deployments.some((d) => d.name === context_.config.deployment)) {
                        throw new SoloError(ErrorMessages.DEPLOYMENT_NAME_ALREADY_EXISTS(context_.config.deployment));
                    }
                },
            },
            {
                title: 'Add deployment to local config',
                task: async (context_, task) => {
                    const { namespace, deployment, realm, shard } = context_.config;
                    task.title = `Adding deployment: ${deployment} with namespace: ${namespace.name} to local config`;
                    if (this.localConfig.configuration.deployments.some((d) => d.name === deployment)) {
                        throw new SoloError(`Deployment ${deployment} is already added to local config`);
                    }
                    const actualDeployment = this.localConfig.configuration.deployments.addNew();
                    actualDeployment.name = deployment;
                    actualDeployment.namespace = namespace.name;
                    actualDeployment.realm = realm;
                    actualDeployment.shard = shard;
                    await this.localConfig.persist();
                },
            },
        ], {
            concurrent: false,
            rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION,
        }, undefined, DeploymentCommand_1.CREATE_COMMAND);
        if (tasks.isRoot()) {
            try {
                await tasks.run();
            }
            catch (error) {
                throw new SoloError('Error creating deployment', error);
            }
        }
        return true;
    }
    /**
     * Delete a deployment from the local config
     */
    async delete(argv) {
        const self = this;
        const tasks = new Listr([
            {
                title: 'Initialize',
                task: async (context_, task) => {
                    await self.localConfig.load();
                    await self.remoteConfig.loadAndValidate(argv);
                    self.configManager.update(argv);
                    await self.configManager.executePrompt(task, [flags.deployment]);
                    context_.config = {
                        quiet: self.configManager.getFlag(flags.quiet),
                        deployment: self.configManager.getFlag(flags.deployment),
                    };
                    if (!self.localConfig.configuration.deployments ||
                        !self.localConfig.configuration.deployments.some((d) => d.name === context_.config.deployment)) {
                        throw new SoloError(ErrorMessages.DEPLOYMENT_NAME_ALREADY_EXISTS(context_.config.deployment));
                    }
                },
            },
            {
                title: 'Check for existing remote resources',
                task: async (context_) => {
                    const { deployment } = context_.config;
                    const clusterReferences = self.localConfig.configuration.deploymentByName(deployment).clusters;
                    for (const clusterReference of clusterReferences) {
                        const context = self.localConfig.configuration.clusterRefs.get(clusterReference.toString()).toString();
                        const namespace = NamespaceName.of(self.localConfig.configuration.deploymentByName(deployment).namespace);
                        const remoteConfigExists = await self.remoteConfig.remoteConfigExists(namespace, context);
                        const namespaceExists = await self.k8Factory.getK8(context).namespaces().has(namespace);
                        const existingConfigMaps = await self.k8Factory
                            .getK8(context)
                            .configMaps()
                            .list(namespace, ['app.kubernetes.io/managed-by=Helm']);
                        if (remoteConfigExists || namespaceExists || existingConfigMaps.length > 0) {
                            throw new SoloError(`Deployment ${deployment} has remote resources in cluster: ${clusterReference}`);
                        }
                    }
                },
            },
            {
                title: 'Remove deployment from local config',
                task: async (context_) => {
                    const { deployment } = context_.config;
                    const actualDeployment = this.localConfig.configuration.deploymentByName(deployment);
                    if (actualDeployment) {
                        this.localConfig.configuration.deployments.remove(actualDeployment);
                    }
                    await this.localConfig.persist();
                },
            },
        ], {
            concurrent: false,
            rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION,
        });
        try {
            await tasks.run();
        }
        catch (error) {
            throw new SoloError('Error deleting deployment', error);
        }
        return true;
    }
    /**
     * Add new cluster for specified deployment, and create or edit the remote config
     */
    async addCluster(argv) {
        const self = this;
        const tasks = this.taskList.newTaskList([
            self.initializeClusterAddConfig(argv),
            self.verifyClusterAddArgs(),
            self.checkNetworkState(),
            self.testClusterConnection(),
            self.verifyClusterAddPrerequisites(),
            self.addClusterRefToDeployments(),
            self.createOrEditRemoteConfigForNewDeployment(argv),
        ], {
            concurrent: false,
            rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION,
        }, undefined, DeploymentCommand_1.ADD_COMMAND);
        if (tasks.isRoot()) {
            try {
                await tasks.run();
            }
            catch (error) {
                throw new SoloError('Error adding cluster to deployment', error);
            }
        }
        return true;
    }
    async list(argv) {
        const self = this;
        const tasks = new Listr([
            {
                title: 'Initialize',
                task: async (context_, task) => {
                    await self.localConfig.load();
                    self.configManager.update(argv);
                    await self.configManager.executePrompt(task, [flags.clusterRef]);
                    context_.config = {
                        clusterName: self.configManager.getFlag(flags.clusterRef),
                    };
                },
            },
            {
                title: 'Validate context',
                task: async (context_) => {
                    const clusterName = context_.config.clusterName;
                    const context = self.localConfig.configuration.clusterRefs.get(clusterName)?.toString();
                    self.k8Factory.default().contexts().updateCurrent(context);
                    const namespaces = await self.k8Factory.default().namespaces().list();
                    const namespacesWithRemoteConfigs = [];
                    for (const namespace of namespaces) {
                        const isFound = await container
                            .resolve(InjectTokens.ClusterChecks)
                            .isRemoteConfigPresentInNamespace(namespace);
                        if (isFound) {
                            namespacesWithRemoteConfigs.push(namespace.name);
                        }
                    }
                    self.logger.showList(`Deployments inside cluster: ${chalk.cyan(clusterName)}`, namespacesWithRemoteConfigs);
                },
            },
        ], {
            concurrent: false,
            rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION,
        });
        try {
            await tasks.run();
        }
        catch (error) {
            throw new SoloError(`Error installing chart ${constants.SOLO_DEPLOYMENT_CHART}`, error);
        }
        return true;
    }
    getCommandDefinition() {
        const self = this;
        return {
            command: DeploymentCommand_1.COMMAND_NAME,
            desc: 'Manage solo network deployment',
            builder: (yargs) => {
                return yargs
                    .command({
                    command: 'create',
                    desc: 'Creates a solo deployment',
                    builder: (y) => {
                        flags.setRequiredCommandFlags(y, ...DeploymentCommand_1.CREATE_FLAGS_LIST.required);
                        flags.setOptionalCommandFlags(y, ...DeploymentCommand_1.CREATE_FLAGS_LIST.optional);
                    },
                    handler: async (argv) => {
                        self.logger.info("==== Running 'deployment create' ===");
                        await self
                            .create(argv)
                            .then(r => {
                            self.logger.info('==== Finished running `deployment create`====');
                            if (!r) {
                                throw new SoloError('Error creating deployment, expected return value to be true');
                            }
                        })
                            .catch(error => {
                            throw new SoloError(`Error creating deployment: ${error.message}`, error);
                        });
                    },
                })
                    .command({
                    command: 'delete',
                    desc: 'Deletes a solo deployment',
                    builder: (y) => {
                        flags.setRequiredCommandFlags(y, ...DeploymentCommand_1.DELETE_FLAGS_LIST.required);
                        flags.setOptionalCommandFlags(y, ...DeploymentCommand_1.DELETE_FLAGS_LIST.optional);
                    },
                    handler: async (argv) => {
                        self.logger.info("==== Running 'deployment delete' ===");
                        await self
                            .delete(argv)
                            .then(r => {
                            self.logger.info('==== Finished running `deployment delete`====');
                            if (!r) {
                                throw new SoloError('Error deleting deployment, expected return value to be true');
                            }
                        })
                            .catch(error => {
                            throw new SoloError(`Error deleting deployment: ${error.message}`, error);
                        });
                    },
                })
                    .command({
                    command: 'list',
                    desc: 'List solo deployments inside a cluster',
                    builder: (y) => {
                        flags.setRequiredCommandFlags(y, ...DeploymentCommand_1.LIST_DEPLOYMENTS_FLAGS_LIST.required);
                        flags.setOptionalCommandFlags(y, ...DeploymentCommand_1.LIST_DEPLOYMENTS_FLAGS_LIST.optional);
                    },
                    handler: async (argv) => {
                        self.logger.info("==== Running 'deployment list' ===");
                        await self
                            .list(argv)
                            .then(r => {
                            self.logger.info('==== Finished running `deployment list`====');
                            if (!r) {
                                throw new SoloError('Error listing deployments, expected return value to be true');
                            }
                        })
                            .catch(error => {
                            throw new SoloError(`Error listing deployments: ${error.message}`, error);
                        });
                    },
                })
                    .command({
                    command: 'add-cluster',
                    desc: 'Adds cluster to solo deployments',
                    builder: (y) => {
                        flags.setRequiredCommandFlags(y, ...DeploymentCommand_1.ADD_CLUSTER_FLAGS_LIST.required);
                        flags.setOptionalCommandFlags(y, ...DeploymentCommand_1.ADD_CLUSTER_FLAGS_LIST.optional);
                    },
                    handler: async (argv) => {
                        self.logger.info("==== Running 'deployment add-cluster' ===");
                        await self
                            .addCluster(argv)
                            .then(r => {
                            self.logger.info('==== Finished running `deployment add-cluster`====');
                            if (!r) {
                                throw new SoloError('Error adding cluster deployment, expected return value to be true');
                            }
                        })
                            .catch(error => {
                            self.logger.showUserError(error);
                            throw new SoloError(`Error adding cluster deployment: ${error.message}`, error);
                        });
                    },
                })
                    .demandCommand(1, 'Select a chart command');
            },
        };
    }
    async close() { } // no-op
    /**
     * Initializes and populates the config and context for 'deployment add-cluster'
     */
    initializeClusterAddConfig(argv) {
        // eslint-disable-next-line @typescript-eslint/typedef,unicorn/no-this-assignment
        const self = this;
        return {
            title: 'Initialize',
            task: async (context_, task) => {
                await self.localConfig.load();
                this.configManager.update(argv);
                await this.configManager.executePrompt(task, [flags.deployment, flags.clusterRef]);
                context_.config = {
                    quiet: this.configManager.getFlag(flags.quiet),
                    namespace: await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task),
                    deployment: this.configManager.getFlag(flags.deployment),
                    clusterRef: this.configManager.getFlag(flags.clusterRef),
                    enableCertManager: this.configManager.getFlag(flags.enableCertManager),
                    numberOfConsensusNodes: this.configManager.getFlag(flags.numberOfConsensusNodes),
                    dnsBaseDomain: this.configManager.getFlag(flags.dnsBaseDomain),
                    dnsConsensusNodePattern: this.configManager.getFlag(flags.dnsConsensusNodePattern),
                    existingNodesCount: 0,
                    nodeAliases: [],
                    context: '',
                };
            },
        };
    }
    /**
     * Validates:
     * - cluster ref is present in the local config's cluster-ref => context mapping
     * - the deployment is created
     * - the cluster-ref is not already added to the deployment
     */
    verifyClusterAddArgs() {
        return {
            title: 'Verify args',
            task: async (context_) => {
                const { clusterRef, deployment } = context_.config;
                if (!this.localConfig.configuration.clusterRefs.get(clusterRef)) {
                    throw new SoloError(`Cluster ref ${clusterRef} not found in local config`);
                }
                context_.config.context = this.localConfig.configuration.clusterRefs.get(clusterRef)?.toString();
                if (!this.localConfig.configuration.deploymentByName(deployment)) {
                    throw new SoloError(`Deployment ${deployment} not found in local config`);
                }
                if (this.localConfig.configuration.deploymentByName(deployment).clusters.includes(new StringFacade(clusterRef))) {
                    throw new SoloError(`Cluster ref ${clusterRef} is already added for deployment`);
                }
            },
        };
    }
    /**
     * Checks the ledger phase:
     * - if remote config is found check's the ledgerPhase field to see if it's pre or post genesis.
     *   - pre genesis:
     *     - prompts user if needed.
     *     - generates node aliases based on '--number-of-consensus-nodes'
     *   - post genesis:
     *     - throws if '--number-of-consensus-nodes' is passed
     * - if remote config is not found:
     *   - prompts user if needed.
     *   - generates node aliases based on '--number-of-consensus-nodes'.
     */
    checkNetworkState() {
        return {
            title: 'check ledger phase',
            task: async (context_, task) => {
                const { deployment, numberOfConsensusNodes, quiet, namespace } = context_.config;
                const existingClusterReferences = this.localConfig.configuration.deploymentByName(deployment).clusters;
                // if there is no remote config don't validate deployment ledger phase
                if (existingClusterReferences.length === 0) {
                    context_.config.ledgerPhase = LedgerPhase.UNINITIALIZED;
                    // if the user can't be prompted for '--num-consensus-nodes' fail
                    if (!numberOfConsensusNodes && quiet) {
                        throw new SoloError(`--${flags.numberOfConsensusNodes} must be specified ${DeploymentStates.PRE_GENESIS}`);
                    }
                    // prompt the user for the '--num-consensus-nodes'
                    else if (!numberOfConsensusNodes) {
                        await this.configManager.executePrompt(task, [flags.numberOfConsensusNodes]);
                        context_.config.numberOfConsensusNodes = this.configManager.getFlag(flags.numberOfConsensusNodes);
                    }
                    context_.config.nodeAliases = Templates.renderNodeAliasesFromCount(context_.config.numberOfConsensusNodes, 0);
                    return;
                }
                const existingClusterContext = this.localConfig.configuration.clusterRefs
                    .get(existingClusterReferences.get(0)?.toString())
                    ?.toString();
                context_.config.existingClusterContext = existingClusterContext;
                await this.remoteConfig.populateFromExisting(namespace, existingClusterContext);
                const ledgerPhase = this.remoteConfig.configuration.state.ledgerPhase;
                context_.config.ledgerPhase = ledgerPhase;
                const existingNodesCount = Object.keys(this.remoteConfig.configuration.state.consensusNodes).length;
                context_.config.nodeAliases = Templates.renderNodeAliasesFromCount(numberOfConsensusNodes, existingNodesCount);
                // If ledgerPhase is pre-genesis and user can't be prompted for the '--num-consensus-nodes' fail
                if (ledgerPhase === LedgerPhase.UNINITIALIZED && !numberOfConsensusNodes && quiet) {
                    throw new SoloError(`--${flags.numberOfConsensusNodes} must be specified ${LedgerPhase.UNINITIALIZED}`);
                }
                // If ledgerPhase is pre-genesis prompt the user for the '--num-consensus-nodes'
                else if (ledgerPhase === LedgerPhase.UNINITIALIZED && !numberOfConsensusNodes) {
                    await this.configManager.executePrompt(task, [flags.numberOfConsensusNodes]);
                    context_.config.numberOfConsensusNodes = this.configManager.getFlag(flags.numberOfConsensusNodes);
                    context_.config.nodeAliases = Templates.renderNodeAliasesFromCount(context_.config.numberOfConsensusNodes, existingNodesCount);
                }
                // if the ledgerPhase is post-genesis and '--num-consensus-nodes' is specified throw
                else if (ledgerPhase === LedgerPhase.INITIALIZED && numberOfConsensusNodes) {
                    throw new SoloError(`--${flags.numberOfConsensusNodes.name}=${numberOfConsensusNodes} shouldn't be specified ${ledgerPhase}`);
                }
            },
        };
    }
    /**
     * Tries to connect with the cluster using the context from the local config
     */
    testClusterConnection() {
        return {
            title: 'Test cluster connection',
            task: async (context_, task) => {
                const { clusterRef, context } = context_.config;
                task.title += `: ${clusterRef}, context: ${context}`;
                const isConnected = await this.k8Factory
                    .getK8(context)
                    .namespaces()
                    .list()
                    .then(() => true)
                    .catch(() => false);
                if (!isConnected) {
                    throw new SoloError(`Connection failed for cluster ${clusterRef} with context: ${context}`);
                }
            },
        };
    }
    verifyClusterAddPrerequisites() {
        return {
            title: 'Verify prerequisites',
            task: async () => {
                // TODO: Verifies Kubernetes cluster & namespace-level prerequisites (e.g., cert-manager, HAProxy, etc.)
            },
        };
    }
    /**
     * Adds the new cluster-ref for the deployment in local config
     */
    addClusterRefToDeployments() {
        return {
            title: 'add cluster-ref in local config deployments',
            task: async (context_, task) => {
                const { clusterRef, deployment } = context_.config;
                task.title = `add cluster-ref: ${clusterRef} for deployment: ${deployment} in local config`;
                this.localConfig.configuration.deploymentByName(deployment).clusters.add(new StringFacade(clusterRef));
                await this.localConfig.persist();
            },
        };
    }
    /**
     * - if remote config not found, create new remote config for the deployment.
     * - if remote config is found, add the new data for the deployment.
     */
    createOrEditRemoteConfigForNewDeployment(argv) {
        return {
            title: 'create remote config for deployment',
            task: async (context_, task) => {
                const { deployment, clusterRef, context, ledgerPhase, nodeAliases, namespace, existingClusterContext, dnsBaseDomain, dnsConsensusNodePattern, } = context_.config;
                argv[flags.nodeAliasesUnparsed.name] = nodeAliases.join(',');
                task.title += `: ${deployment} in cluster: ${clusterRef}`;
                if (!(await this.k8Factory.getK8(context).namespaces().has(namespace))) {
                    await this.k8Factory.getK8(context).namespaces().create(namespace);
                }
                await (existingClusterContext
                    ? this.remoteConfig.createFromExisting(namespace, clusterRef, deployment, this.componentFactory, dnsBaseDomain, dnsConsensusNodePattern, existingClusterContext, argv, nodeAliases)
                    : this.remoteConfig.create(argv, ledgerPhase, nodeAliases, namespace, deployment, clusterRef, context, dnsBaseDomain, dnsConsensusNodePattern));
            },
        };
    }
};
DeploymentCommand = DeploymentCommand_1 = __decorate([
    injectable(),
    __param(0, inject(InjectTokens.ClusterCommandTasks)),
    __param(1, inject(InjectTokens.ComponentFactory)),
    __metadata("design:paramtypes", [Function, Object])
], DeploymentCommand);
export { DeploymentCommand };
//# sourceMappingURL=deployment.js.map