// 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 BlockNodeCommand_1;
import { SoloError } from '../core/errors/solo-error.js';
import * as helpers from '../core/helpers.js';
import { checkDockerImageExists, showVersionBanner, sleep } from '../core/helpers.js';
import * as constants from '../core/constants.js';
import { BaseCommand } from './base.js';
import { Flags as flags } from './flags.js';
import { ListrLock } from '../core/lock/listr-lock.js';
import * as versions from '../../version.js';
import { MINIMUM_HIERO_BLOCK_NODE_VERSION_FOR_NEW_LIVENESS_CHECK_PORT } from '../../version.js';
import { ContainerReference } from '../integration/kube/resources/container/container-reference.js';
import { Duration } from '../core/time/duration.js';
import chalk from 'chalk';
import { ComponentTypes } from '../core/config/remote/enumerations/component-types.js';
import { gte, lt, SemVer } from 'semver';
import { injectable } from 'tsyringe-neo';
import { Templates } from '../core/templates.js';
import { Version } from '../business/utils/version.js';
import { PvcReference } from '../integration/kube/resources/pvc/pvc-reference.js';
import { PvcName } from '../integration/kube/resources/pvc/pvc-name.js';
import { LedgerPhase } from '../data/schema/model/remote/ledger-phase.js';
import { NetworkCommand } from './network.js';
import { ExternalBlockNodeStateSchema } from '../data/schema/model/remote/state/external-block-node-state-schema.js';
let BlockNodeCommand = class BlockNodeCommand extends BaseCommand {
    static { BlockNodeCommand_1 = this; }
    constructor() {
        super();
    }
    static ADD_CONFIGS_NAME = 'addConfigs';
    static DESTROY_CONFIGS_NAME = 'destroyConfigs';
    static UPGRADE_CONFIGS_NAME = 'upgradeConfigs';
    static ADD_EXTERNAL_CONFIGS_NAME = 'addExternalConfigs';
    static DELETE_CONFIGS_NAME = 'deleteExternalConfigs';
    static ADD_FLAGS_LIST = {
        required: [flags.deployment],
        optional: [
            flags.blockNodeChartVersion,
            flags.blockNodeChartDirectory,
            flags.chartDirectory,
            flags.clusterRef,
            flags.devMode,
            flags.domainName,
            flags.enableIngress,
            flags.quiet,
            flags.valuesFile,
            flags.releaseTag,
            flags.imageTag,
            flags.priorityMapping,
        ],
    };
    static ADD_EXTERNAL_FLAGS_LIST = {
        required: [flags.deployment, flags.externalBlockNodeAddress],
        optional: [flags.clusterRef, flags.devMode, flags.quiet, flags.priorityMapping],
    };
    static DELETE_EXTERNAL_FLAGS_LIST = {
        required: [flags.deployment],
        optional: [flags.clusterRef, flags.devMode, flags.force, flags.quiet, flags.id],
    };
    static DESTROY_FLAGS_LIST = {
        required: [flags.deployment],
        optional: [flags.chartDirectory, flags.clusterRef, flags.devMode, flags.force, flags.quiet, flags.id],
    };
    static UPGRADE_FLAGS_LIST = {
        required: [flags.deployment],
        optional: [
            flags.chartDirectory,
            flags.blockNodeChartDirectory,
            flags.clusterRef,
            flags.devMode,
            flags.force,
            flags.quiet,
            flags.valuesFile,
            flags.upgradeVersion,
            flags.id,
        ],
    };
    async prepareValuesArgForBlockNode(config) {
        let valuesArgument = '';
        valuesArgument += helpers.prepareValuesFiles(constants.BLOCK_NODE_VALUES_FILE);
        if (config.valuesFile) {
            valuesArgument += helpers.prepareValuesFiles(config.valuesFile);
        }
        valuesArgument += helpers.populateHelmArguments({ nameOverride: config.releaseName });
        // Only handle domainName and imageTag for deploy config (not upgrade config)
        if ('domainName' in config && config.domainName) {
            valuesArgument += helpers.populateHelmArguments({
                'ingress.enabled': true,
                'ingress.hosts[0].host': config.domainName,
                'ingress.hosts[0].paths[0].path': '/',
                'ingress.hosts[0].paths[0].pathType': 'ImplementationSpecific',
            });
        }
        if ('imageTag' in config && config.imageTag) {
            config.imageTag = Version.getValidSemanticVersion(config.imageTag, false, 'Block node image tag');
            if (!checkDockerImageExists(constants.BLOCK_NODE_IMAGE_NAME, config.imageTag)) {
                throw new SoloError(`Local block node image with tag "${config.imageTag}" does not exist.`);
            }
            // use local image from docker engine
            valuesArgument += helpers.populateHelmArguments({
                'image.repository': constants.BLOCK_NODE_IMAGE_NAME,
                'image.tag': config.imageTag,
                'image.pullPolicy': 'Never',
            });
        }
        const { state, clusters } = this.remoteConfig.configuration;
        for (const [index, blockNode] of state.blockNodes.entries()) {
            const cluster = clusters.find(({ name }) => name === blockNode.metadata.cluster);
            const fqdn = Templates.renderSvcFullyQualifiedDomainName(`block-node-${blockNode.metadata.id}`, config.namespace.name, cluster.dnsBaseDomain);
            valuesArgument += helpers.populateHelmArguments({
                [`blockNode.sources[${index}].address`]: fqdn,
                [`blockNode.sources[${index}].port`]: constants.BLOCK_NODE_PORT,
                [`blockNode.sources[${index}].priority`]: 1,
            });
        }
        return valuesArgument;
    }
    getReleaseName() {
        return this.renderReleaseName(this.remoteConfig.configuration.components.getNewComponentId(ComponentTypes.BlockNode));
    }
    renderReleaseName(id) {
        if (typeof id !== 'number') {
            throw new SoloError(`Invalid component id: ${id}, type: ${typeof id}`);
        }
        return `${constants.BLOCK_NODE_RELEASE_NAME}-${id}`;
    }
    updateConsensusNodesInRemoteConfig() {
        return {
            title: 'Update consensus nodes in remote config',
            task: async ({ config: { newBlockNodeComponent, priorityMapping } }) => {
                const state = this.remoteConfig.configuration.state;
                const nodeAliases = Object.keys(priorityMapping);
                for (const node of state.consensusNodes.filter((node) => nodeAliases.includes(Templates.renderNodeAliasFromNumber(node.metadata.id)))) {
                    const priority = priorityMapping[Templates.renderNodeAliasFromNumber(node.metadata.id)];
                    node.blockNodeMap.push([newBlockNodeComponent.metadata.id, priority]);
                }
                await this.remoteConfig.persist();
            },
        };
    }
    updateConsensusNodesPostGenesis() {
        return {
            title: 'Copy block-nodes.json to consensus nodes',
            task: async ({ config: { priorityMapping } }) => {
                const nodeAliases = Object.keys(priorityMapping);
                const filteredConsensusNodes = this.remoteConfig
                    .getConsensusNodes()
                    .filter((node) => nodeAliases.includes(node.name));
                for (const node of filteredConsensusNodes) {
                    await NetworkCommand.createAndCopyBlockNodeJsonFileForConsensusNode(node, this.logger, this.k8Factory);
                }
            },
        };
    }
    updateConsensusNodesPostGenesisForExternal() {
        return {
            title: 'Copy block-nodes.json to consensus nodes',
            task: async ({ config: { priorityMapping } }) => {
                const nodeAliases = Object.keys(priorityMapping);
                const filteredConsensusNodes = this.remoteConfig
                    .getConsensusNodes()
                    .filter((node) => nodeAliases.includes(node.name));
                for (const node of filteredConsensusNodes) {
                    await NetworkCommand.createAndCopyBlockNodeJsonFileForConsensusNode(node, this.logger, this.k8Factory);
                }
            },
        };
    }
    handleConsensusNodeUpdating() {
        return {
            title: 'Update consensus nodes',
            task: (_, task) => {
                const subTasks = [this.updateConsensusNodesInRemoteConfig()];
                if (this.remoteConfig.configuration.state.ledgerPhase !== LedgerPhase.UNINITIALIZED) {
                    subTasks.push(this.updateConsensusNodesPostGenesis());
                }
                return task.newListr(subTasks, constants.LISTR_DEFAULT_OPTIONS.DEFAULT);
            },
        };
    }
    updateConsensusNodesInRemoteConfigForExternalBlockNode() {
        return {
            title: 'Update consensus nodes in remote config',
            task: async ({ config: { newExternalBlockNodeComponent, priorityMapping } }) => {
                const state = this.remoteConfig.configuration.state;
                const nodeAliases = Object.keys(priorityMapping);
                for (const node of state.consensusNodes.filter((node) => nodeAliases.includes(Templates.renderNodeAliasFromNumber(node.metadata.id)))) {
                    const priority = priorityMapping[Templates.renderNodeAliasFromNumber(node.metadata.id)];
                    node.externalBlockNodeMap.push([newExternalBlockNodeComponent.id, priority]);
                }
                this.remoteConfig.configuration.state.consensusNodes = state.consensusNodes;
                await this.remoteConfig.persist();
            },
        };
    }
    handleConsensusNodeUpdatingForExternalBlockNode() {
        return {
            title: 'Update consensus nodes',
            task: (_, task) => {
                const subTasks = [
                    this.updateConsensusNodesInRemoteConfigForExternalBlockNode(),
                ];
                if (this.remoteConfig.configuration.state.ledgerPhase !== LedgerPhase.UNINITIALIZED) {
                    subTasks.push(this.updateConsensusNodesPostGenesisForExternal());
                }
                return task.newListr(subTasks, constants.LISTR_DEFAULT_OPTIONS.DEFAULT);
            },
        };
    }
    async add(argv) {
        let lease;
        const tasks = this.taskList.newTaskList([
            {
                title: 'Initialize',
                task: async (context_, task) => {
                    await this.localConfig.load();
                    await this.remoteConfig.loadAndValidate(argv);
                    lease = await this.leaseManager.create();
                    this.configManager.update(argv);
                    flags.disablePrompts(BlockNodeCommand_1.ADD_FLAGS_LIST.optional);
                    const allFlags = [
                        ...BlockNodeCommand_1.ADD_FLAGS_LIST.required,
                        ...BlockNodeCommand_1.ADD_FLAGS_LIST.optional,
                    ];
                    await this.configManager.executePrompt(task, allFlags);
                    const config = this.configManager.getConfig(BlockNodeCommand_1.ADD_CONFIGS_NAME, allFlags);
                    context_.config = config;
                    // check if block node version compatible with current hedera platform version
                    let consensusNodeVersion = this.remoteConfig.configuration.versions.consensusNode.toString();
                    if (consensusNodeVersion === '0.0.0') {
                        // if is possible block node deployed before consensus node, then use release tag as fallback
                        consensusNodeVersion = config.releaseTag;
                    }
                    const currentVersion = new SemVer(consensusNodeVersion);
                    const minimumVersion = new SemVer(versions.MINIMUM_HIERO_PLATFORM_VERSION_FOR_BLOCK_NODE);
                    if (lt(currentVersion, minimumVersion)) {
                        throw new SoloError(`Current version is ${consensusNodeVersion}, Hedera platform versions less than ${versions.MINIMUM_HIERO_PLATFORM_VERSION_FOR_BLOCK_NODE_LEGACY_RELEASE} are not supported`);
                    }
                    config.namespace = await this.getNamespace(task);
                    config.clusterRef = this.getClusterReference();
                    config.context = this.getClusterContext(config.clusterRef);
                    config.priorityMapping = Templates.parseBlockNodePriorityMapping(config.priorityMapping, this.remoteConfig.getConsensusNodes());
                    const currentBlockNodeVersion = new SemVer(config.chartVersion);
                    if (lt(new SemVer(consensusNodeVersion), new SemVer(versions.MINIMUM_HIERO_PLATFORM_VERSION_FOR_BLOCK_NODE)) &&
                        gte(currentBlockNodeVersion, MINIMUM_HIERO_BLOCK_NODE_VERSION_FOR_NEW_LIVENESS_CHECK_PORT)) {
                        throw new SoloError(`Current platform version is ${consensusNodeVersion}, Hedera platform version less than ${versions.MINIMUM_HIERO_PLATFORM_VERSION_FOR_BLOCK_NODE} ` +
                            `are not supported for block node version ${MINIMUM_HIERO_BLOCK_NODE_VERSION_FOR_NEW_LIVENESS_CHECK_PORT.version}`);
                    }
                    config.chartVersion = Version.getValidSemanticVersion(config.chartVersion, false, 'Block node chart version');
                    config.livenessCheckPort = this.getLivenessCheckPortNumber(config.chartVersion, config.imageTag);
                    return ListrLock.newAcquireLockTask(lease, task);
                },
            },
            {
                title: 'Prepare release name and block node name',
                task: async ({ config }) => {
                    config.releaseName = this.getReleaseName();
                    config.newBlockNodeComponent = this.componentFactory.createNewBlockNodeComponent(config.clusterRef, config.namespace);
                },
            },
            {
                title: 'Prepare chart values',
                task: async ({ config }) => {
                    config.valuesArg = await this.prepareValuesArgForBlockNode(config);
                },
            },
            {
                title: 'Deploy block node',
                task: async ({ config }, task) => {
                    const { context, namespace, releaseName, chartVersion, valuesArg, clusterRef, imageTag, blockNodeChartDirectory, } = config;
                    await this.chartManager.install(namespace, releaseName, constants.BLOCK_NODE_CHART, blockNodeChartDirectory || constants.BLOCK_NODE_CHART_URL, chartVersion, valuesArg, context);
                    if (imageTag) {
                        // update config map with new VERSION info since
                        // it will be used as a critical environment variable by block node
                        const blockNodeStateSchema = this.componentFactory.createNewBlockNodeComponent(clusterRef, namespace);
                        const blockNodeId = blockNodeStateSchema.metadata.id;
                        const name = `block-node-${blockNodeId}-config`;
                        const data = { VERSION: imageTag };
                        await this.k8Factory.getK8(context).configMaps().update(namespace, name, data);
                        task.title += ` with local built image (${imageTag})`;
                    }
                    showVersionBanner(this.logger, releaseName, chartVersion);
                    await this.updateBlockNodeVersionInRemoteConfig(config);
                },
            },
            {
                title: 'Check block node pod is running',
                task: async ({ config }) => {
                    await this.k8Factory
                        .getK8(config.context)
                        .pods()
                        .waitForRunningPhase(config.namespace, Templates.renderBlockNodeLabels(config.newBlockNodeComponent.metadata.id), constants.BLOCK_NODE_PODS_RUNNING_MAX_ATTEMPTS, constants.BLOCK_NODE_PODS_RUNNING_DELAY);
                },
            },
            {
                title: 'Check software',
                task: async ({ config: { newBlockNodeComponent, context, namespace } }) => {
                    const labels = Templates.renderBlockNodeLabels(newBlockNodeComponent.metadata.id);
                    const blockNodePods = await this.k8Factory.getK8(context).pods().list(namespace, labels);
                    if (blockNodePods.length === 0) {
                        throw new SoloError('Failed to list block node pod');
                    }
                },
            },
            {
                title: 'Check block node pod is ready',
                task: async ({ config }) => {
                    try {
                        await this.k8Factory
                            .getK8(config.context)
                            .pods()
                            .waitForReadyStatus(config.namespace, Templates.renderBlockNodeLabels(config.newBlockNodeComponent.metadata.id), constants.BLOCK_NODE_PODS_RUNNING_MAX_ATTEMPTS, constants.BLOCK_NODE_PODS_RUNNING_DELAY);
                    }
                    catch (error) {
                        throw new SoloError(`Block node ${config.releaseName} is not ready: ${error.message}`, error);
                    }
                },
            },
            this.checkBlockNodeReadiness(),
            this.addBlockNodeComponent(),
            this.handleConsensusNodeUpdating(),
        ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'block node add');
        if (tasks.isRoot()) {
            try {
                await tasks.run();
            }
            catch (error) {
                throw new SoloError(`Error deploying block node: ${error.message}`, error);
            }
            finally {
                await lease?.release();
            }
        }
        else {
            this.taskList.registerCloseFunction(async () => {
                await lease?.release();
            });
        }
        return true;
    }
    async destroy(argv) {
        let lease;
        const tasks = this.taskList.newTaskList([
            {
                title: 'Initialize',
                task: async (context_, task) => {
                    await this.localConfig.load();
                    await this.remoteConfig.loadAndValidate(argv);
                    lease = await this.leaseManager.create();
                    this.configManager.update(argv);
                    flags.disablePrompts(BlockNodeCommand_1.DESTROY_FLAGS_LIST.optional);
                    const allFlags = [
                        ...BlockNodeCommand_1.DESTROY_FLAGS_LIST.required,
                        ...BlockNodeCommand_1.DESTROY_FLAGS_LIST.optional,
                    ];
                    await this.configManager.executePrompt(task, allFlags);
                    const config = this.configManager.getConfig(BlockNodeCommand_1.DESTROY_CONFIGS_NAME, allFlags);
                    context_.config = config;
                    config.namespace = await this.getNamespace(task);
                    config.clusterRef = this.getClusterReference();
                    config.context = this.getClusterContext(config.clusterRef);
                    const { id, releaseName, isChartInstalled, isLegacyChartInstalled } = await this.inferDestroyData(config.id, config.namespace, config.context);
                    config.id = id;
                    config.releaseName = releaseName;
                    config.isChartInstalled = isChartInstalled;
                    config.isLegacyChartInstalled = isLegacyChartInstalled;
                    await this.throwIfNamespaceIsMissing(config.context, config.namespace);
                    return ListrLock.newAcquireLockTask(lease, task);
                },
            },
            {
                title: 'Destroy block node',
                task: async ({ config: { namespace, releaseName, context } }) => {
                    await this.chartManager.uninstall(namespace, releaseName, context);
                    const podReferences = await this.k8Factory
                        .getK8(context)
                        .pvcs()
                        .list(namespace, [`app.kubernetes.io/instance=${releaseName}`])
                        .then((pvcs) => pvcs.map((pvc) => PvcName.of(pvc)))
                        .then((names) => names.map((pvc) => PvcReference.of(namespace, pvc)));
                    for (const podReference of podReferences) {
                        await this.k8Factory.getK8(context).pvcs().delete(podReference);
                    }
                },
                skip: ({ config }) => !config.isChartInstalled,
            },
            this.removeBlockNodeComponentFromRemoteConfig(),
            this.rebuildBlockNodesJsonForConsensusNodes(),
        ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'block node destroy');
        if (tasks.isRoot()) {
            try {
                await tasks.run();
            }
            catch (error) {
                throw new SoloError(`Error destroying block node: ${error.message}`, error);
            }
            finally {
                await lease?.release();
            }
        }
        else {
            this.taskList.registerCloseFunction(async () => {
                await lease?.release();
            });
        }
        return true;
    }
    async upgrade(argv) {
        let lease;
        const tasks = this.taskList.newTaskList([
            {
                title: 'Initialize',
                task: async (context_, task) => {
                    await this.localConfig.load();
                    await this.remoteConfig.loadAndValidate(argv);
                    lease = await this.leaseManager.create();
                    this.configManager.update(argv);
                    flags.disablePrompts(BlockNodeCommand_1.UPGRADE_FLAGS_LIST.optional);
                    const allFlags = [
                        ...BlockNodeCommand_1.UPGRADE_FLAGS_LIST.required,
                        ...BlockNodeCommand_1.UPGRADE_FLAGS_LIST.optional,
                    ];
                    await this.configManager.executePrompt(task, allFlags);
                    const config = this.configManager.getConfig(BlockNodeCommand_1.UPGRADE_CONFIGS_NAME, allFlags);
                    context_.config = config;
                    config.namespace = await this.getNamespace(task);
                    config.clusterRef = this.getClusterReference();
                    config.context = this.getClusterContext(config.clusterRef);
                    config.id = this.inferBlockNodeId(config.id);
                    config.isLegacyChartInstalled = await this.checkIfLegacyChartIsInstalled(config.id, config.namespace, config.context);
                    config.releaseName = config.isLegacyChartInstalled
                        ? `${constants.BLOCK_NODE_RELEASE_NAME}-0`
                        : this.renderReleaseName(config.id);
                    config.context = this.remoteConfig.getClusterRefs()[config.clusterRef];
                    config.upgradeVersion ||= versions.BLOCK_NODE_VERSION;
                    return ListrLock.newAcquireLockTask(lease, task);
                },
            },
            {
                title: 'Look-up block node',
                task: async ({ config }) => {
                    try {
                        this.remoteConfig.configuration.components.getComponent(ComponentTypes.BlockNode, config.id);
                    }
                    catch (error) {
                        throw new SoloError(`Block node ${config.releaseName} was not found`, error);
                    }
                },
            },
            {
                title: 'Prepare chart values',
                task: async ({ config }) => {
                    config.valuesArg = await this.prepareValuesArgForBlockNode(config);
                },
            },
            {
                title: 'Update block node chart',
                task: async ({ config }) => {
                    const { namespace, releaseName, context, upgradeVersion } = config;
                    const validatedUpgradeVersion = Version.getValidSemanticVersion(upgradeVersion, false, 'Block node chart version');
                    await this.chartManager.upgrade(namespace, releaseName, constants.BLOCK_NODE_CHART, config.blockNodeChartDirectory || constants.BLOCK_NODE_CHART_URL, validatedUpgradeVersion, config.valuesArg, context, false);
                    showVersionBanner(this.logger, constants.BLOCK_NODE_CHART, config.upgradeVersion);
                    await this.updateBlockNodeVersionInRemoteConfig(config);
                },
            },
        ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'block node upgrade');
        if (tasks.isRoot()) {
            try {
                await tasks.run();
            }
            catch (error) {
                throw new SoloError(`Error upgrading block node: ${error.message}`, error);
            }
            finally {
                await lease?.release();
            }
        }
        else {
            this.taskList.registerCloseFunction(async () => {
                await lease?.release();
            });
        }
        return true;
    }
    async addExternal(argv) {
        let lease;
        const tasks = this.taskList.newTaskList([
            {
                title: 'Initialize',
                task: async (context_, task) => {
                    await this.localConfig.load();
                    await this.remoteConfig.loadAndValidate(argv);
                    lease = await this.leaseManager.create();
                    this.configManager.update(argv);
                    flags.disablePrompts(BlockNodeCommand_1.ADD_EXTERNAL_FLAGS_LIST.optional);
                    const allFlags = [
                        ...BlockNodeCommand_1.ADD_EXTERNAL_FLAGS_LIST.required,
                        ...BlockNodeCommand_1.ADD_EXTERNAL_FLAGS_LIST.optional,
                    ];
                    await this.configManager.executePrompt(task, allFlags);
                    const config = this.configManager.getConfig(BlockNodeCommand_1.ADD_EXTERNAL_CONFIGS_NAME, allFlags);
                    context_.config = config;
                    config.clusterRef = this.getClusterReference();
                    config.context = this.getClusterContext(config.clusterRef);
                    config.namespace = await this.getNamespace(task);
                    config.priorityMapping = Templates.parseBlockNodePriorityMapping(config.priorityMapping, this.remoteConfig.getConsensusNodes());
                    const id = this.remoteConfig.configuration.state.externalBlockNodes.length + 1;
                    const [address, port] = Templates.parseExternalBlockAddress(config.externalBlockNodeAddress);
                    config.newExternalBlockNodeComponent = new ExternalBlockNodeStateSchema(id, address, port);
                    this.logger.showUser('Configuring external block node, ' +
                        `${chalk.grey('ID')} ${chalk.cyan(`[${id}]`)}, ` +
                        `${chalk.grey('address')} ${chalk.cyan(`[${address}:${port}]`)} `);
                    return ListrLock.newAcquireLockTask(lease, task);
                },
            },
            this.addExternalBlockNodeComponent(),
            this.handleConsensusNodeUpdatingForExternalBlockNode(),
        ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'block node add-external');
        if (tasks.isRoot()) {
            try {
                await tasks.run();
            }
            catch (error) {
                throw new SoloError(`Error adding external block node: ${error.message}`, error);
            }
            finally {
                await lease?.release();
            }
        }
        else {
            this.taskList.registerCloseFunction(async () => {
                await lease?.release();
            });
        }
        return true;
    }
    async deleteExternal(argv) {
        let lease;
        const tasks = this.taskList.newTaskList([
            {
                title: 'Initialize',
                task: async (context_, task) => {
                    await this.localConfig.load();
                    await this.remoteConfig.loadAndValidate(argv);
                    lease = await this.leaseManager.create();
                    this.configManager.update(argv);
                    flags.disablePrompts(BlockNodeCommand_1.DELETE_EXTERNAL_FLAGS_LIST.optional);
                    const allFlags = [
                        ...BlockNodeCommand_1.DELETE_EXTERNAL_FLAGS_LIST.required,
                        ...BlockNodeCommand_1.DELETE_EXTERNAL_FLAGS_LIST.optional,
                    ];
                    await this.configManager.executePrompt(task, allFlags);
                    const config = this.configManager.getConfig(BlockNodeCommand_1.DELETE_CONFIGS_NAME, allFlags);
                    context_.config = config;
                    config.namespace = await this.getNamespace(task);
                    config.clusterRef = this.getClusterReference();
                    config.context = this.getClusterContext(config.clusterRef);
                    config.id = this.inferExternalBlockNodeId(config.id);
                    await this.throwIfNamespaceIsMissing(config.context, config.namespace);
                    return ListrLock.newAcquireLockTask(lease, task);
                },
            },
            this.removeExternalBlockNodeComponent(),
            this.rebuildBlockNodesJsonForConsensusNodes(),
        ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'block node delete-external');
        if (tasks.isRoot()) {
            try {
                await tasks.run();
            }
            catch (error) {
                throw new SoloError(`Error removing external block node: ${error.message}`, error);
            }
            finally {
                await lease?.release();
            }
        }
        else {
            this.taskList.registerCloseFunction(async () => {
                await lease?.release();
            });
        }
        return true;
    }
    rebuildBlockNodesJsonForConsensusNodes() {
        return {
            title: "Rebuild 'block.nodes.json' for consensus nodes",
            skip: () => this.remoteConfig.configuration.state.ledgerPhase === LedgerPhase.UNINITIALIZED,
            task: async () => {
                for (const node of this.remoteConfig.getConsensusNodes()) {
                    await NetworkCommand.createAndCopyBlockNodeJsonFileForConsensusNode(node, this.logger, this.k8Factory);
                }
            },
        };
    }
    /**
     * Gives the port used for liveness check based on the chart version and image tag (if set)
     */
    getLivenessCheckPortNumber(chartVersion, imageTag) {
        let useLegacyPort = false;
        chartVersion = typeof chartVersion === 'string' ? new SemVer(chartVersion) : chartVersion;
        imageTag = typeof imageTag === 'string' && imageTag ? new SemVer(imageTag) : undefined;
        if (lt(chartVersion, versions.MINIMUM_HIERO_BLOCK_NODE_VERSION_FOR_NEW_LIVENESS_CHECK_PORT)) {
            useLegacyPort = true;
        }
        else if (imageTag && lt(imageTag, versions.MINIMUM_HIERO_BLOCK_NODE_VERSION_FOR_NEW_LIVENESS_CHECK_PORT)) {
            useLegacyPort = true;
        }
        return useLegacyPort ? constants.BLOCK_NODE_PORT_LEGACY : constants.BLOCK_NODE_PORT;
    }
    async updateBlockNodeVersionInRemoteConfig(config) {
        let blockNodeVersion;
        let imageTag;
        if (config.hasOwnProperty('upgradeVersion') && config.upgradeVersion) {
            const version = config.upgradeVersion;
            blockNodeVersion = typeof version === 'string' ? new SemVer(version) : version;
        }
        if (config.hasOwnProperty('chartVersion') && config.chartVersion) {
            const version = config.chartVersion;
            blockNodeVersion = typeof version === 'string' ? new SemVer(version) : version;
        }
        if (config.hasOwnProperty('imageTag') && config.imageTag) {
            const tag = config.imageTag;
            imageTag = typeof tag === 'string' ? new SemVer(tag) : tag;
        }
        const finalVersion = imageTag && lt(blockNodeVersion, imageTag) ? imageTag : blockNodeVersion;
        this.remoteConfig.updateComponentVersion(ComponentTypes.BlockNode, finalVersion);
        await this.remoteConfig.persist();
    }
    /** Adds the block node component to remote config. */
    addBlockNodeComponent() {
        return {
            title: 'Add block node component in remote config',
            skip: () => !this.remoteConfig.isLoaded(),
            task: async ({ config }) => {
                this.remoteConfig.configuration.components.addNewComponent(config.newBlockNodeComponent, ComponentTypes.BlockNode);
                await this.remoteConfig.persist();
            },
        };
    }
    /** Adds the block node component to remote config. */
    addExternalBlockNodeComponent() {
        return {
            title: 'Add external block node component in remote config',
            skip: () => !this.remoteConfig.isLoaded(),
            task: async ({ config: { newExternalBlockNodeComponent } }) => {
                this.remoteConfig.configuration.state.externalBlockNodes.push(newExternalBlockNodeComponent);
                await this.remoteConfig.persist();
            },
        };
    }
    /** Adds the block node component to remote config. */
    removeBlockNodeComponentFromRemoteConfig() {
        return {
            title: 'Disable block node component in remote config',
            skip: () => !this.remoteConfig.isLoaded(),
            task: async ({ config }) => {
                this.remoteConfig.configuration.components.removeComponent(config.id, ComponentTypes.BlockNode);
                for (const node of this.remoteConfig.configuration.state.consensusNodes) {
                    node.blockNodeMap = node.blockNodeMap.filter(([id]) => id !== config.id);
                }
                await this.remoteConfig.persist();
            },
        };
    }
    /** Adds the block node component to remote config. */
    removeExternalBlockNodeComponent() {
        return {
            title: 'Remove block node component from remote config',
            skip: () => !this.remoteConfig.isLoaded(),
            task: async ({ config }) => {
                this.remoteConfig.configuration.state.externalBlockNodes =
                    this.remoteConfig.configuration.state.externalBlockNodes.filter((component) => component.id !== config.id);
                for (const node of this.remoteConfig.configuration.state.consensusNodes) {
                    node.externalBlockNodeMap = node.externalBlockNodeMap.filter(([id]) => id !== config.id);
                }
                await this.remoteConfig.persist();
            },
        };
    }
    displayHealthCheckData(task) {
        const baseTitle = task.title;
        return function (attempt, maxAttempt, color = 'yellow', additionalData = '') {
            task.title = `${baseTitle} - ${chalk[color](`[${attempt}/${maxAttempt}]`)} ${chalk[color](additionalData)}`;
        };
    }
    checkBlockNodeReadiness() {
        return {
            title: 'Check block node readiness',
            task: async ({ config }, task) => {
                const displayHealthcheckCallback = this.displayHealthCheckData(task);
                const blockNodePodReference = await this.k8Factory
                    .getK8(config.context)
                    .pods()
                    .list(config.namespace, Templates.renderBlockNodeLabels(config.newBlockNodeComponent.metadata.id))
                    .then((pods) => pods[0].podReference);
                const containerReference = ContainerReference.of(blockNodePodReference, constants.BLOCK_NODE_CONTAINER_NAME);
                const maxAttempts = constants.BLOCK_NODE_ACTIVE_MAX_ATTEMPTS;
                let attempt = 1;
                let success = false;
                displayHealthcheckCallback(attempt, maxAttempts);
                while (attempt < maxAttempts) {
                    try {
                        const response = await helpers.withTimeout(this.k8Factory
                            .getK8(config.context)
                            .containers()
                            .readByRef(containerReference)
                            .execContainer(['bash', '-c', `curl -s http://localhost:${config.livenessCheckPort}/healthz/readyz`]), Duration.ofSeconds(constants.BLOCK_NODE_ACTIVE_TIMEOUT), 'Healthcheck timed out');
                        if (response !== 'OK') {
                            throw new SoloError('Bad response status');
                        }
                        success = true;
                        break;
                    }
                    catch (error) {
                        this.logger.debug(`Waiting for block node health check to come back with OK status: ${error.message}, [attempts: ${attempt}/${maxAttempts}`);
                    }
                    attempt++;
                    await sleep(Duration.ofSeconds(constants.BLOCK_NODE_ACTIVE_DELAY));
                    displayHealthcheckCallback(attempt, maxAttempts);
                }
                if (!success) {
                    displayHealthcheckCallback(attempt, maxAttempts, 'red', 'max attempts reached');
                    throw new SoloError('Max attempts reached');
                }
                displayHealthcheckCallback(attempt, maxAttempts, 'green', 'success');
            },
        };
    }
    async close() { } // no-op
    inferBlockNodeId(id) {
        if (typeof id === 'number') {
            return id;
        }
        if (this.remoteConfig.configuration.components.state.blockNodes.length === 0) {
            throw new SoloError('Block node not found in remote config.' + id ? `ID ${id}` : '');
        }
        return this.remoteConfig.configuration.components.state.blockNodes[0].metadata.id;
    }
    inferExternalBlockNodeId(id) {
        if (typeof id === 'number') {
            return id;
        }
        if (this.remoteConfig.configuration.components.state.externalBlockNodes.length === 0) {
            throw new SoloError('No External block node not found in remote config. ' + id ? `ID ${id}` : '');
        }
        return this.remoteConfig.configuration.components.state.externalBlockNodes[0].id;
    }
    async checkIfLegacyChartIsInstalled(id, namespace, context) {
        return id === 1
            ? await this.chartManager.isChartInstalled(namespace, `${constants.BLOCK_NODE_RELEASE_NAME}-0`, context)
            : false;
    }
    async inferDestroyData(id, namespace, context) {
        id = this.inferBlockNodeId(id);
        const isLegacyChartInstalled = await this.checkIfLegacyChartIsInstalled(id, namespace, context);
        if (isLegacyChartInstalled) {
            return {
                id,
                releaseName: `${constants.BLOCK_NODE_RELEASE_NAME}-0`,
                isChartInstalled: true,
                isLegacyChartInstalled,
            };
        }
        const releaseName = this.renderReleaseName(id);
        return {
            id,
            releaseName,
            isChartInstalled: await this.chartManager.isChartInstalled(namespace, releaseName, context),
            isLegacyChartInstalled,
        };
    }
};
BlockNodeCommand = BlockNodeCommand_1 = __decorate([
    injectable(),
    __metadata("design:paramtypes", [])
], BlockNodeCommand);
export { BlockNodeCommand };
export default BlockNodeCommand;
//# sourceMappingURL=block-node.js.map