// SPDX-License-Identifier: Apache-2.0

import * as helpers from '../../core/helpers.js';
import * as NodeFlags from './flags.js';
import {type NodeCommandConfigs} from './configs.js';
import * as constants from '../../core/constants.js';
import {type LockManager} from '../../core/lock/lock-manager.js';
import {SoloError} from '../../core/errors/solo-error.js';
import {type Lock} from '../../core/lock/lock.js';
import {LeaseWrapper, type NodeCommandTasks} from './tasks.js';
import {NodeSubcommandType} from '../../core/enumerations.js';
import {NodeHelper} from './helper.js';
import {AnyListrContext, type ArgvStruct, type NodeAlias, type NodeAliases} from '../../types/aliases.js';
import chalk from 'chalk';
import {type ComponentId, type Optional, type SoloListr, type SoloListrTask} from '../../types/index.js';
import {inject, injectable} from 'tsyringe-neo';
import {patchInject} from '../../core/dependency-injection/container-helper.js';
import {CommandHandler} from '../../core/command-handler.js';
import {type NamespaceName} from '../../types/namespace/namespace-name.js';
import {type ConsensusNode} from '../../core/model/consensus-node.js';
import {InjectTokens} from '../../core/dependency-injection/inject-tokens.js';
import {type NodeDestroyContext} from './config-interfaces/node-destroy-context.js';
import {type NodeAddContext} from './config-interfaces/node-add-context.js';
import {type NodeUpdateContext} from './config-interfaces/node-update-context.js';
import {type NodeUpgradeContext} from './config-interfaces/node-upgrade-context.js';
import {ComponentTypes} from '../../core/config/remote/enumerations/component-types.js';
import {DeploymentPhase} from '../../data/schema/model/remote/deployment-phase.js';
import {Templates} from '../../core/templates.js';
import {ConsensusNodeStateSchema} from '../../data/schema/model/remote/state/consensus-node-state-schema.js';
import {type RemoteConfigRuntimeStateApi} from '../../business/runtime-state/api/remote-config-runtime-state-api.js';
import {ComponentsDataWrapperApi} from '../../core/config/remote/api/components-data-wrapper-api.js';
import {LedgerPhase} from '../../data/schema/model/remote/ledger-phase.js';
import {LocalConfigRuntimeState} from '../../business/runtime-state/config/local/local-config-runtime-state.js';
import {type Zippy} from '../../core/zippy.js';
import {PathEx} from '../../business/utils/path-ex.js';
import {Flags as flags} from '../flags.js';
import {select as selectPrompt} from '@inquirer/prompts';
import {Deployment} from '../../business/runtime-state/config/local/deployment.js';
import {MutableFacadeArray} from '../../business/runtime-state/collection/mutable-facade-array.js';
import {DeploymentSchema} from '../../data/schema/model/local/deployment-schema.js';
import {type ConfigManager} from '../../core/config-manager.js';
import {getSoloVersion} from '../../../version.js';
import {DiagnosticsReporter} from '../util/diagnostics-reporter.js';
import {findDeploymentsFromRemoteConfig} from '../util/find-deployments-from-remote-config.js';
import {GetSoloRemoteConfigMapTask} from '../util/get-solo-remote-config-map-task.js';
import {type RemoteDeploymentInfo} from '../util/remote-deployment-info.js';
import {type K8Factory} from '../../integration/kube/k8-factory.js';

@injectable()
export class NodeCommandHandlers extends CommandHandler {
  private readonly nodeConfigManager: ConfigManager;

  public constructor(
    @inject(InjectTokens.LockManager) private readonly leaseManager: LockManager,
    @inject(InjectTokens.ConfigManager) configManager: ConfigManager,
    @inject(InjectTokens.LocalConfigRuntimeState) private readonly localConfig: LocalConfigRuntimeState,
    @inject(InjectTokens.RemoteConfigRuntimeState) private readonly remoteConfig: RemoteConfigRuntimeStateApi,
    @inject(InjectTokens.NodeCommandTasks) private readonly tasks: NodeCommandTasks,
    @inject(InjectTokens.NodeCommandConfigs) private readonly configs: NodeCommandConfigs,
    @inject(InjectTokens.K8Factory) private readonly k8Factory: K8Factory,
    @inject(InjectTokens.Zippy) private readonly zippy?: Zippy,
  ) {
    super();
    this.leaseManager = patchInject(leaseManager, InjectTokens.LockManager, this.constructor.name);
    this.nodeConfigManager = patchInject(configManager, InjectTokens.ConfigManager, this.constructor.name);
    this.configs = patchInject(configs, InjectTokens.NodeCommandConfigs, this.constructor.name);
    this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name);
    this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name);
    this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name);
    this.tasks = patchInject(tasks, InjectTokens.NodeCommandTasks, this.constructor.name);
    this.zippy = patchInject(zippy, InjectTokens.Zippy, this.constructor.name);
  }

  private static readonly ADD_CONTEXT_FILE: string = 'node-add.json';
  private static readonly DESTROY_CONTEXT_FILE: string = 'node-destroy.json';
  private static readonly UPDATE_CONTEXT_FILE: string = 'node-update.json';
  private static readonly UPGRADE_CONTEXT_FILE: string = 'node-upgrade.json';

  private resolveOutputDirectory(argv: ArgvStruct, fallback: string = ''): string {
    this.nodeConfigManager.update(argv);
    return this.nodeConfigManager.getFlag<string>(flags.outputDir) || fallback;
  }

  private resolveDeploymentFlag(argv: ArgvStruct): string {
    const deploymentFromArgument: string = (argv[flags.deployment.name] as string) || '';
    if (deploymentFromArgument) {
      return deploymentFromArgument;
    }

    this.nodeConfigManager.update(argv);
    return this.nodeConfigManager.getFlag<string>(flags.deployment) || '';
  }

  private resolveQuietFlag(argv: ArgvStruct): boolean {
    if (argv[flags.quiet.name] !== undefined) {
      return argv[flags.quiet.name] === true;
    }

    this.nodeConfigManager.update(argv);
    return this.nodeConfigManager.getFlag<boolean>(flags.quiet) === true;
  }

  private ensureInteractiveSelectionPrompt(): void {
    if (!process.stdout.isTTY || !process.stdin.isTTY) {
      throw new SoloError('Cannot prompt for input in non-interactive mode');
    }
  }

  /** ******** Task Lists **********/

  private destroyPrepareTaskList(argv: ArgvStruct, lease: Lock): SoloListrTask<NodeDestroyContext>[] {
    return [
      this.tasks.initialize(argv, this.configs.destroyConfigBuilder.bind(this.configs), lease),
      this.validateSingleNodeState({excludedPhases: []}),
      this.tasks.identifyExistingNodes(),
      this.tasks.loadAdminKey(),
      this.tasks.prepareUpgradeZip(),
      this.tasks.checkExistingNodesStakedAmount(),
    ];
  }

  private destroySubmitTransactionsTaskList(): SoloListrTask<NodeDestroyContext>[] {
    return [
      this.tasks.sendNodeDeleteTransaction(),
      this.tasks.sendPrepareUpgradeTransaction() as SoloListrTask<NodeDestroyContext>,
      this.tasks.sendFreezeUpgradeTransaction() as SoloListrTask<NodeDestroyContext>,
    ];
  }

  private destroyExecuteTaskList(): SoloListrTask<NodeDestroyContext>[] {
    return [
      this.tasks.checkAllNodesAreFrozen('existingNodeAliases'),
      this.tasks.stopNodes('existingNodeAliases'),
      this.tasks.downloadNodeGeneratedFilesForDynamicAddressBook(),
      this.tasks.prepareStagingDirectory('existingNodeAliases'),
      this.tasks.refreshNodeList(),
      this.tasks.copyNodeKeysToSecrets('refreshedConsensusNodes'),
      this.tasks.getNodeLogsAndConfigs(),
      this.tasks.updateChartWithConfigMap(
        'Delete network node from chart and update configMaps',
        NodeSubcommandType.DESTROY,
      ),
      this.tasks.killNodes(NodeSubcommandType.DESTROY),
      this.tasks.sleep('Give time for pods to come up after being killed', 20_000),
      this.tasks.checkNodePodsAreRunning(),
      this.tasks.populateServiceMap(),
      this.tasks.fetchPlatformSoftware('allNodeAliases'),
      this.tasks.setupNetworkNodes('allNodeAliases', false),
      this.tasks.startNodes('allNodeAliases'),
      this.tasks.enablePortForwarding(),
      this.tasks.checkAllNodesAreActive('allNodeAliases'),
      this.tasks.checkAllNodeProxiesAreActive(),
      this.tasks.triggerStakeWeightCalculate<NodeDestroyContext>(NodeSubcommandType.DESTROY),
      this.tasks.finalize(),
    ];
  }

  private addPrepareTasks(argv: ArgvStruct, lease: Lock): SoloListrTask<NodeAddContext>[] {
    return [
      this.tasks.initialize(argv, this.configs.addConfigBuilder.bind(this.configs), lease),
      // TODO instead of validating the state we need to do a remote config add component, and we will need to manually
      //  the nodeAlias based on the next available node ID + 1
      // this.validateSingleNodeState({excludedPhases: []}),
      this.tasks.checkPVCsEnabled(),
      this.tasks.identifyExistingNodes(),
      this.tasks.determineNewNodeAccountNumber(),
      this.tasks.copyGrpcTlsCertificates(),
      this.tasks.generateGossipKey(),
      this.tasks.generateGrpcTlsKey(),
      this.tasks.loadSigningKeyCertificate(),
      this.tasks.computeMTLSCertificateHash(),
      this.tasks.prepareGossipEndpoints(),
      this.tasks.prepareGrpcServiceEndpoints(),
      this.tasks.prepareUpgradeZip(),
      this.tasks.checkExistingNodesStakedAmount(),
    ];
  }

  private addSubmitTransactionsTasks(): SoloListrTask<NodeAddContext>[] {
    return [
      this.tasks.sendNodeCreateTransaction(),
      this.tasks.sendPrepareUpgradeTransaction() as SoloListrTask<NodeAddContext>,
      this.tasks.sendFreezeUpgradeTransaction() as SoloListrTask<NodeAddContext>,
    ];
  }

  private addExecuteTasks(): SoloListrTask<NodeAddContext>[] {
    return [
      this.tasks.checkAllNodesAreFrozen('existingNodeAliases'),
      this.tasks.downloadNodeGeneratedFilesForDynamicAddressBook(),
      this.tasks.prepareStagingDirectory('allNodeAliases'),
      this.tasks.addNewConsensusNodeToRemoteConfig(),
      this.tasks.copyNodeKeysToSecrets(),
      this.tasks.getNodeLogsAndConfigs(),
      this.tasks.updateChartWithConfigMap('Deploy new network node', NodeSubcommandType.ADD),
      this.tasks.stopNodes('existingNodeAliases'),
      this.tasks.killNodes(),
      this.tasks.checkNodePodsAreRunning(),
      this.tasks.populateServiceMap(),
      this.tasks.fetchPlatformSoftware('allNodeAliases'),
      this.tasks.downloadLastState(),
      this.tasks.uploadStateToNewNode(),
      this.tasks.setupNetworkNodes('allNodeAliases', false),
      this.tasks.updateBlockNodesJson(),
      this.tasks.addWrapsLib(),
      this.tasks.startNodes('allNodeAliases'),
      this.tasks.enablePortForwarding(),
      this.tasks.checkAllNodesAreActive('allNodeAliases'),
      this.tasks.checkAllNodeProxiesAreActive(),
      this.tasks.waitForTss(),
      this.tasks.stakeNewNode(),
      this.tasks.triggerStakeWeightCalculate<NodeAddContext>(NodeSubcommandType.ADD),
      this.tasks.loadAdminKey(),
      this.tasks.setGrpcWebEndpoint('newNodeAliases', NodeSubcommandType.ADD),
      this.tasks.finalize(),
    ];
  }

  private updatePrepareTasks(argv: ArgvStruct, lease: Lock): SoloListrTask<NodeUpdateContext>[] {
    return [
      this.tasks.initialize(argv, this.configs.updateConfigBuilder.bind(this.configs), lease),
      this.validateSingleNodeState({excludedPhases: []}),
      this.tasks.identifyExistingNodes(),
      this.tasks.loadAdminKey(),
      this.tasks.prepareUpgradeZip(),
      this.tasks.checkExistingNodesStakedAmount(),
    ];
  }

  private updateSubmitTransactionsTasks(): SoloListrTask<NodeUpdateContext>[] {
    return [
      this.tasks.sendNodeUpdateTransaction(),
      this.tasks.sendPrepareUpgradeTransaction() as SoloListrTask<NodeUpdateContext>,
      this.tasks.sendFreezeUpgradeTransaction() as SoloListrTask<NodeUpdateContext>,
    ];
  }

  private updateExecuteTasks(): SoloListrTask<NodeUpdateContext>[] {
    return [
      this.tasks.checkAllNodesAreFrozen('existingNodeAliases'),
      this.tasks.downloadNodeGeneratedFilesForDynamicAddressBook(),
      this.tasks.prepareStagingDirectory('allNodeAliases'),
      this.tasks.copyNodeKeysToSecrets(),
      this.tasks.getNodeLogsAndConfigs(),
      this.tasks.updateChartWithConfigMap(
        'Update chart to use new configMap due to account number change',
        NodeSubcommandType.UPDATE,
        ({config}): boolean => !config.newAccountNumber && !config.debugNodeAlias,
      ),
      this.tasks.killNodesAndUpdateConfigMap(),
      this.tasks.checkNodePodsAreRunning(),
      this.tasks.fetchPlatformSoftware('allNodeAliases'),
      this.tasks.setupNetworkNodes('allNodeAliases', false),
      this.tasks.addWrapsLib(),
      this.tasks.startNodes('allNodeAliases'),
      this.tasks.enablePortForwarding(),
      this.tasks.checkAllNodesAreActive('allNodeAliases'),
      this.tasks.checkAllNodeProxiesAreActive(),
      this.tasks.triggerStakeWeightCalculate<NodeUpdateContext>(NodeSubcommandType.UPDATE),
      this.tasks.finalize(),
    ];
  }

  private upgradePrepareTasks(argv: ArgvStruct, lease: Lock): SoloListrTask<NodeUpgradeContext>[] {
    return [
      this.tasks.initialize(argv, this.configs.upgradeConfigBuilder.bind(this.configs), lease),
      this.validateAllNodePhases({excludedPhases: []}),
      this.tasks.identifyExistingNodes(),
      this.tasks.loadAdminKey(),
      this.tasks.prepareUpgradeZip(),
      this.tasks.checkExistingNodesStakedAmount(),
    ];
  }

  private upgradeSubmitTransactionsTasks(): SoloListrTask<NodeUpgradeContext>[] {
    return [
      this.tasks.sendPrepareUpgradeTransaction() as SoloListrTask<NodeUpgradeContext>,
      this.tasks.sendFreezeUpgradeTransaction() as SoloListrTask<NodeUpgradeContext>,
    ];
  }

  private upgradeExecuteTasks(): SoloListrTask<NodeUpgradeContext>[] {
    return [
      this.tasks.checkAllNodesAreFrozen('existingNodeAliases'),
      this.tasks.downloadNodeUpgradeFiles(),
      this.tasks.getNodeLogsAndConfigs(),
      this.tasks.upgradeNodeConfigurationFilesWithChart(),
      this.tasks.fetchPlatformSoftware('nodeAliases'),
      this.tasks.addWrapsLib(),
      this.tasks.startNodes('allNodeAliases'),
      this.tasks.enablePortForwarding(),
      this.tasks.checkAllNodesAreActive('allNodeAliases'),
      this.tasks.checkAllNodeProxiesAreActive(),
      this.tasks.finalize(),
    ];
  }

  /** ******** Handlers **********/

  public async prepareUpgrade(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.PREPARE_UPGRADE_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(argv, this.configs.prepareUpgradeConfigBuilder.bind(this.configs), leaseWrapper.lease),
        this.tasks.identifyExistingNodes(),
        this.tasks.prepareStagingDirectory('existingNodeAliases'),
        this.tasks.prepareUpgradeZip(),
        this.tasks.sendPrepareUpgradeTransaction(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in preparing node upgrade',
      leaseWrapper.lease,
    );

    return true;
  }

  public async freezeUpgrade(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.PREPARE_UPGRADE_FLAGS);

    await this.commandAction(
      argv,
      [
        this.tasks.initialize(argv, this.configs.prepareUpgradeConfigBuilder.bind(this.configs)),
        this.tasks.identifyExistingNodes(),
        this.tasks.prepareUpgradeZip(),
        this.tasks.sendFreezeUpgradeTransaction(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in executing node freeze upgrade',
    );

    return true;
  }

  public async update(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        ...this.updatePrepareTasks(argv, leaseWrapper.lease),
        ...this.updateSubmitTransactionsTasks(),
        ...this.updateExecuteTasks(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in updating consensus nodes',
      leaseWrapper.lease,
    );

    return true;
  }

  public async updatePrepare(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_PREPARE_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        ...this.updatePrepareTasks(argv, leaseWrapper.lease),
        this.tasks.saveContextData(argv, NodeCommandHandlers.UPDATE_CONTEXT_FILE, NodeHelper.updateSaveContextParser),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in preparing consensus node update',
      leaseWrapper.lease,
    );

    return true;
  }

  public async updateSubmitTransactions(argv: ArgvStruct): Promise<boolean> {
    const leaseWrapper: LeaseWrapper = {lease: undefined};
    argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_SUBMIT_TRANSACTIONS_FLAGS);

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(argv, this.configs.updateConfigBuilder.bind(this.configs), leaseWrapper.lease),
        this.tasks.loadContextData(argv, NodeCommandHandlers.UPDATE_CONTEXT_FILE, NodeHelper.updateLoadContextParser),
        ...this.updateSubmitTransactionsTasks(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in submitting transactions for consensus node update',
      leaseWrapper.lease,
    );

    return true;
  }

  public async updateExecute(argv: ArgvStruct): Promise<boolean> {
    const leaseWrapper: LeaseWrapper = {lease: undefined};
    argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_EXECUTE_FLAGS);

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(
          argv,
          this.configs.updateConfigBuilder.bind(this.configs),
          leaseWrapper.lease,

          false,
        ),
        this.tasks.loadContextData(argv, NodeCommandHandlers.UPDATE_CONTEXT_FILE, NodeHelper.updateLoadContextParser),
        ...this.updateExecuteTasks(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in executing network upgrade',
      leaseWrapper.lease,
    );

    return true;
  }

  public async upgradePrepare(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_PREPARE_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};
    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        ...this.upgradePrepareTasks(argv, leaseWrapper.lease),
        this.tasks.saveContextData(argv, NodeCommandHandlers.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeSaveContextParser),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in preparing node upgrade',
      leaseWrapper.lease,
    );
    return true;
  }

  public async upgradeSubmitTransactions(argv: ArgvStruct): Promise<boolean> {
    const leaseWrapper: LeaseWrapper = {lease: undefined};
    argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_SUBMIT_TRANSACTIONS_FLAGS);

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(argv, this.configs.upgradeConfigBuilder.bind(this.configs), leaseWrapper.lease),
        this.tasks.loadContextData(argv, NodeCommandHandlers.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeLoadContextParser),
        ...this.upgradeSubmitTransactionsTasks(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in submitting transactions for node upgrade',
      leaseWrapper.lease,
    );

    return true;
  }

  public async upgradeExecute(argv: ArgvStruct): Promise<boolean> {
    const leaseWrapper: LeaseWrapper = {lease: undefined};
    argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_FLAGS);
    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(
          argv,
          this.configs.upgradeConfigBuilder.bind(this.configs),
          leaseWrapper.lease,

          false,
        ),
        this.tasks.loadContextData(argv, NodeCommandHandlers.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeLoadContextParser),
        ...this.upgradeExecuteTasks(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in executing network upgrade',
      leaseWrapper.lease,
    );

    return true;
  }

  public async upgrade(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};
    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        ...this.upgradePrepareTasks(argv, leaseWrapper.lease),
        ...this.upgradeSubmitTransactionsTasks(),
        ...this.upgradeExecuteTasks(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in upgrade network',
      leaseWrapper.lease,
    );

    return true;
  }

  public async destroy(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.DESTROY_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};
    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        ...this.destroyPrepareTaskList(argv, leaseWrapper.lease),
        ...this.destroySubmitTransactionsTaskList(),
        ...this.destroyExecuteTaskList(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in destroying nodes',
      leaseWrapper.lease,
    );

    return true;
  }

  public async destroyPrepare(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.DESTROY_PREPARE_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        ...this.destroyPrepareTaskList(argv, leaseWrapper.lease),
        this.tasks.saveContextData(argv, NodeCommandHandlers.DESTROY_CONTEXT_FILE, NodeHelper.deleteSaveContextParser),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in preparing to destroy a node',
      leaseWrapper.lease,
    );

    return true;
  }

  public async destroySubmitTransactions(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.DESTROY_SUBMIT_TRANSACTIONS_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(argv, this.configs.destroyConfigBuilder.bind(this.configs), leaseWrapper.lease),
        this.tasks.loadContextData(argv, NodeCommandHandlers.DESTROY_CONTEXT_FILE, NodeHelper.deleteLoadContextParser),
        ...this.destroySubmitTransactionsTaskList(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in deleting a node',
      leaseWrapper.lease,
    );

    return true;
  }

  public async destroyExecute(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.DESTROY_EXECUTE_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(argv, this.configs.destroyConfigBuilder.bind(this.configs), leaseWrapper.lease, false),
        this.tasks.loadContextData(argv, NodeCommandHandlers.DESTROY_CONTEXT_FILE, NodeHelper.deleteLoadContextParser),
        ...this.destroyExecuteTaskList(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in deleting a node',
      leaseWrapper.lease,
    );

    return true;
  }

  public async add(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        ...this.addPrepareTasks(argv, leaseWrapper.lease),
        ...this.addSubmitTransactionsTasks(),
        ...this.addExecuteTasks(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in adding consensus node',
      leaseWrapper.lease,
    );

    return true;
  }

  public async addPrepare(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_PREPARE_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        ...this.addPrepareTasks(argv, leaseWrapper.lease),
        this.tasks.saveContextData(argv, NodeCommandHandlers.ADD_CONTEXT_FILE, helpers.addSaveContextParser),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in preparing node',
      leaseWrapper.lease,
    );

    return true;
  }

  public async addSubmitTransactions(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_SUBMIT_TRANSACTIONS_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(argv, this.configs.addConfigBuilder.bind(this.configs), leaseWrapper.lease),
        this.tasks.loadContextData(argv, NodeCommandHandlers.ADD_CONTEXT_FILE, helpers.addLoadContextParser),
        ...this.addSubmitTransactionsTasks(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      '`Error in submitting transactions to node',
      leaseWrapper.lease,
    );

    return true;
  }

  public async addExecute(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_EXECUTE_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(
          argv,
          this.configs.addConfigBuilder.bind(this.configs),
          leaseWrapper.lease,

          false,
        ),
        this.tasks.identifyExistingNodes(),
        this.tasks.loadContextData(argv, NodeCommandHandlers.ADD_CONTEXT_FILE, helpers.addLoadContextParser),
        ...this.addExecuteTasks(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in adding node',
      leaseWrapper.lease,
    );

    return true;
  }

  public async logs(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.LOGS_FLAGS);
    if (!argv[flags.deployment.name]) {
      argv[flags.deployment.name] = await this.resolveDeploymentForLogs(argv);
    }

    const outputDirectory: string = this.resolveOutputDirectory(argv);

    await this.commandAction(
      argv,
      [
        this.tasks.initialize(argv, this.configs.logsConfigBuilder.bind(this.configs), null, true, false),
        this.tasks.getNodeLogsAndConfigs(undefined, outputDirectory),
        this.tasks.getHelmChartValues(outputDirectory),
        GetSoloRemoteConfigMapTask.getTask(this.k8Factory, this.logger, outputDirectory),
        this.tasks.downloadHieroComponentLogs(outputDirectory),
        this.tasks.analyzeCollectedDiagnostics(outputDirectory),
        this.tasks.reportActivePortForwards(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in downloading logs from nodes',
    );

    this.logger.showUser(
      chalk.yellow(
        '\n⚠  Warning: Collected diagnostic data contains sensitive node configuration\n' +
          '   (TLS certificates, private keys, onboard data). Store it securely and do\n' +
          '   not share publicly without reviewing the contents first.',
      ),
    );

    return true;
  }

  public async analyze(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.ANALYZE_FLAGS);

    this.nodeConfigManager.update(argv);
    const inputDirectory: string = this.nodeConfigManager.getFlag<string>(flags.inputDir) || '';

    await this.commandAction(
      argv,
      [this.tasks.analyzeCollectedDiagnostics(inputDirectory)],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error analyzing diagnostics logs',
    );

    return true;
  }

  private async resolveDeploymentForLogs(argv: ArgvStruct): Promise<string> {
    const deploymentFromFlag: string = this.resolveDeploymentFlag(argv);
    if (deploymentFromFlag && deploymentFromFlag.trim()) {
      return deploymentFromFlag;
    }

    await this.localConfig.load();
    const deployments: MutableFacadeArray<Deployment, DeploymentSchema> = this.localConfig.configuration.deployments;
    const validDeployments: Deployment[] = [];
    for (const deployment of deployments) {
      if (deployment?.name && deployment.name.trim().length > 0) {
        validDeployments.push(deployment);
      }
    }

    if (validDeployments.length === 0) {
      const remoteDeployments: Map<string, RemoteDeploymentInfo> = await findDeploymentsFromRemoteConfig(
        this.k8Factory,
        this.logger,
      );
      if (remoteDeployments.size === 0) {
        throw new SoloError(
          `No deployments found in local or remote config. Please provide --${flags.deployment.name} or create a deployment first.`,
        );
      }

      const remoteDeploymentNames: string[] = [...remoteDeployments.keys()];

      if (remoteDeploymentNames.length === 1) {
        const selectedFromRemote: string = remoteDeploymentNames[0];
        this.logger.showUser(`Using deployment from remote config: ${selectedFromRemote}`);
        return selectedFromRemote;
      }

      if (this.resolveQuietFlag(argv)) {
        const names: string = remoteDeploymentNames.join(', ');
        throw new SoloError(
          `Multiple deployments found in remote config (${names}). Please provide --${flags.deployment.name}.`,
        );
      }

      this.ensureInteractiveSelectionPrompt();
      const selectedFromRemote: string = (await selectPrompt({
        message: 'Select deployment for diagnostics logs:',
        choices: remoteDeploymentNames.map((name: string) => ({name, value: name})),
      })) as string;
      this.logger.showUser(`Using selected deployment: ${selectedFromRemote}`);
      return selectedFromRemote;
    }

    if (validDeployments.length === 1) {
      const deploymentName: string = validDeployments[0].name;
      this.logger.showUser(`Using deployment from local config: ${deploymentName}`);
      return deploymentName;
    }

    if (this.resolveQuietFlag(argv)) {
      const deploymentNames: string = validDeployments.map((deployment: Deployment) => deployment.name).join(', ');
      throw new SoloError(
        `Multiple deployments found in local config (${deploymentNames}). Please provide --${flags.deployment.name}.`,
      );
    }

    this.ensureInteractiveSelectionPrompt();
    const selectedDeployment: string = (await selectPrompt({
      message: 'Select deployment for diagnostics logs:',
      choices: validDeployments.map((deployment): {name: string; value: string} => ({
        name: deployment.name,
        value: deployment.name,
      })),
    })) as string;
    this.logger.showUser(`Using selected deployment: ${selectedDeployment}`);
    return selectedDeployment;
  }

  public async all(argv: ArgvStruct, excludeSensitiveData: boolean = false): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.DIAGNOSTICS_CONNECTIONS);
    if (!argv[flags.deployment.name]) {
      argv[flags.deployment.name] = await this.resolveDeploymentForLogs(argv);
    }
    const outputDirectory: string = this.resolveOutputDirectory(argv);
    await this.commandAction(
      argv,
      [
        this.tasks.initialize(argv, this.configs.logsConfigBuilder.bind(this.configs), null, true, false),
        this.tasks.getNodeLogsAndConfigs(excludeSensitiveData, outputDirectory),
        ...(excludeSensitiveData ? [] : [this.tasks.getHelmChartValues(outputDirectory)]),
        GetSoloRemoteConfigMapTask.getTask(this.k8Factory, this.logger, outputDirectory),
        this.tasks.downloadHieroComponentLogs(outputDirectory),
        this.tasks.analyzeCollectedDiagnostics(outputDirectory),
        // do not call validateConnectionsTaskList since node could be stopped or not active but logs are still needed
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in diagnosing deployment',
    );

    return true;
  }

  public async debug(argv: ArgvStruct, excludeSensitiveData: boolean = false): Promise<boolean> {
    // First run all diagnostics
    await this.all(argv, excludeSensitiveData);

    // Then create a zip file from the logs directory
    const outputDirectory: string = this.resolveOutputDirectory(argv, constants.SOLO_LOGS_DIR);
    const deployment: string = this.resolveDeploymentFlag(argv);
    const timestamp: string = new Date().toISOString().replaceAll(':', '-').replaceAll('.', '-').slice(0, 19);
    const zipFileName: string = `solo-debug-${deployment}-${timestamp}.zip`;
    const zipFilePath: string = PathEx.join(outputDirectory, '..', zipFileName);

    this.logger.showUser(chalk.cyan(`\nCreating debug archive from: ${outputDirectory}`));
    this.logger.showUser(chalk.cyan(`Archive location: ${zipFilePath}`));
    if (!excludeSensitiveData) {
      this.logger.showUser(
        chalk.yellow(
          '\n⚠  Warning: The debug archive contains sensitive node configuration\n' +
            '   (TLS certificates, private keys, onboard data). Review its contents\n' +
            '   before sharing. Private keys under data/keys are NOT excluded.',
        ),
      );
    }

    try {
      await this.zippy.zip(outputDirectory, zipFilePath);
      this.logger.showUser(chalk.green('✓ Debug information collected successfully!'));
      this.logger.showUser(chalk.cyan(`  Archive: ${zipFilePath}`));
    } catch (error: Error | unknown) {
      throw new SoloError(`Failed to create debug archive: ${(error as Error).message}`, error as Error);
    }

    return true;
  }

  public async connections(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.DIAGNOSTICS_CONNECTIONS);

    await this.commandAction(
      argv,
      [
        this.tasks.initialize(argv, this.configs.connectionsConfigBuilder.bind(this.configs)),
        ...this.validateConnectionsTaskList(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in testing connections to components',
    );

    return true;
  }

  private validateConnectionsTaskList(): SoloListrTask<AnyListrContext>[] {
    return [
      this.tasks.prepareDiagnosticsData(),
      this.tasks.validateLocalPorts(),
      this.tasks.testAccountCreation(),
      this.tasks.fetchAccountFromExplorer(),
      this.tasks.testRelay(),
    ];
  }

  /**
   * Collects a full debug archive for the deployment (logs + configs + zip) and
   * then creates a GitHub issue using the `gh` CLI with a pre-filled title and body.
   * The generated archive is referenced for the user to attach manually via the GitHub UI.
   *
   */
  public async report(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.REPORT_FLAGS);
    // Resolve deployment before calling collectDebug() so it's available for the issue title/body
    const deployment: string = argv[flags.deployment.name]
      ? String(argv[flags.deployment.name])
      : await this.resolveDeploymentForLogs(argv);
    if (!argv[flags.deployment.name]) {
      argv[flags.deployment.name] = deployment;
    }

    await DiagnosticsReporter.runDiagnosticsReport({
      logger: this.logger,
      deployment,
      outputDirectory: this.resolveOutputDirectory(argv, constants.SOLO_LOGS_DIR),
      soloVersion: getSoloVersion(),
      isQuiet: this.resolveQuietFlag(argv),
      collectDebug: async (): Promise<void> => {
        await this.debug(argv, true);
      },
    });

    return true;
  }

  public async states(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.STATES_FLAGS);

    await this.commandAction(
      argv,
      [
        this.tasks.initialize(argv, this.configs.statesConfigBuilder.bind(this.configs)),
        this.tasks.getNodeStateFiles(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in downloading states from nodes',
    );

    return true;
  }

  public async refresh(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.REFRESH_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(argv, this.configs.refreshConfigBuilder.bind(this.configs), leaseWrapper.lease),
        this.validateAllNodePhases({
          acceptedPhases: [DeploymentPhase.STARTED, DeploymentPhase.CONFIGURED, DeploymentPhase.DEPLOYED],
        }),
        this.tasks.identifyNetworkPods(),
        this.tasks.dumpNetworkNodesSaveState(),
        this.tasks.downloadLastState(),
        this.tasks.uploadStateToNewNode(),
        this.tasks.fetchPlatformSoftware('nodeAliases'),
        this.tasks.setupNetworkNodes('nodeAliases', true),
        this.tasks.startNodes('nodeAliases'),
        this.tasks.checkNodesAndProxiesAreActive('nodeAliases'),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in refreshing nodes',
      leaseWrapper.lease,
    );

    return true;
  }

  public async keys(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.KEYS_FLAGS);

    await this.commandAction(
      argv,
      [
        this.tasks.initialize(argv, this.configs.keysConfigBuilder.bind(this.configs)),
        this.tasks.generateGossipKeys(),
        this.tasks.generateGrpcTlsKeys(),
        this.tasks.finalize(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error generating keys',
      undefined,
      'keys consensus generate',
    );

    return true;
  }

  public async stop(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.STOP_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(argv, this.configs.stopConfigBuilder.bind(this.configs), leaseWrapper.lease),
        this.validateAllNodePhases({
          acceptedPhases: [DeploymentPhase.STARTED, DeploymentPhase.CONFIGURED],
        }),
        this.tasks.identifyNetworkPods(1),
        this.tasks.stopNodes('nodeAliases'),
        this.changeAllNodePhases(DeploymentPhase.STOPPED),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error stopping node',
      leaseWrapper.lease,
    );

    return true;
  }

  public async start(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.START_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(
          argv,
          this.configs.startConfigBuilder.bind(this.configs),
          leaseWrapper.lease,
          true,
          false,
        ),
        this.validateAllNodePhases({acceptedPhases: [DeploymentPhase.CONFIGURED]}),
        this.tasks.identifyExistingNodes(),
        this.tasks.uploadStateFiles(({config}): boolean => config.stateFile.length === 0),
        this.tasks.startNodes('nodeAliases'),
        this.tasks.enablePortForwarding(true),
        this.tasks.checkNodesAndProxiesAreActive('nodeAliases'),
        this.tasks.waitForTss(),
        this.tasks.setGrpcWebEndpoint('nodeAliases', NodeSubcommandType.START),
        this.changeAllNodePhases(DeploymentPhase.STARTED, LedgerPhase.INITIALIZED),
        this.tasks.addNodeStakes(),
        this.tasks.emitNodeStartedEvent(),
        // TODO only show this if we are not running in one-shot mode
        // this.tasks.showUserMessages(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error starting node',
      leaseWrapper.lease,
      'consensus node start',
    );

    return true;
  }

  public async setup(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.SETUP_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(argv, this.configs.setupConfigBuilder.bind(this.configs), leaseWrapper.lease),
        this.validateAllNodePhases({
          acceptedPhases: [DeploymentPhase.DEPLOYED],
        }),
        this.tasks.identifyNetworkPods(),
        this.tasks.fetchPlatformSoftware('nodeAliases'),
        this.tasks.setupNetworkNodes('nodeAliases', true),
        this.tasks.setupNetworkNodeFolders(),
        this.changeAllNodePhases(DeploymentPhase.CONFIGURED),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error in setting up nodes',
      leaseWrapper.lease,
      'consensus node setup',
    );

    return true;
  }

  public async freeze(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.FREEZE_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager, false),
        this.tasks.initialize(
          argv,
          this.configs.freezeConfigBuilder.bind(this.configs),
          leaseWrapper.lease,
          true,
          false,
        ),
        this.tasks.identifyExistingNodes(),
        this.tasks.sendFreezeTransaction(),
        this.tasks.checkAllNodesAreFrozen('existingNodeAliases'),
        this.tasks.stopNodes('existingNodeAliases'),
        this.changeAllNodePhases(DeploymentPhase.FROZEN),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error freezing node',
      leaseWrapper.lease,
    );

    return true;
  }

  public async restart(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.RESTART_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(
          argv,
          this.configs.restartConfigBuilder.bind(this.configs),
          leaseWrapper.lease,
          true,
          false,
        ),
        this.tasks.identifyExistingNodes(),
        this.tasks.addWrapsLib(),
        this.tasks.startNodes('existingNodeAliases'),
        this.tasks.enablePortForwarding(),
        this.tasks.checkNodesAndProxiesAreActive('existingNodeAliases'),
        this.changeAllNodePhases(DeploymentPhase.STARTED),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error restarting node',
      leaseWrapper.lease,
    );

    return true;
  }

  /**
   * Changes the state from all consensus nodes components in remote config.
   *
   * @param phase - to which to change the consensus node component
   * @param ledgerPhase
   */
  public changeAllNodePhases(
    phase: DeploymentPhase,
    ledgerPhase: Optional<LedgerPhase> = undefined,
  ): SoloListrTask<AnyListrContext> {
    interface Context {
      config: {namespace: NamespaceName; consensusNodes: ConsensusNode[]};
    }

    return {
      title: `Change node state to ${phase} in remote config`,
      skip: (): boolean => !this.remoteConfig.isLoaded(),
      task: async (context_: Context): Promise<void> => {
        for (const consensusNode of context_.config.consensusNodes) {
          const componentId: ComponentId = Templates.renderComponentIdFromNodeAlias(consensusNode.name);
          this.remoteConfig.configuration.components.changeNodePhase(componentId, phase);
        }

        if (ledgerPhase) {
          this.remoteConfig.configuration.state.ledgerPhase = ledgerPhase;
        }

        await this.remoteConfig.persist();
      },
    };
  }

  /**
   * Creates tasks to validate that each node state is either one of the accepted states or not one of the excluded.
   *
   * @param acceptedPhases - the state at which the nodes can be, not matching any of the states throws an error
   * @param excludedPhases - the state at which the nodes can't be, matching any of the states throws an error
   */
  public validateAllNodePhases({
    acceptedPhases,
    excludedPhases,
  }: {
    acceptedPhases?: DeploymentPhase[];
    excludedPhases?: DeploymentPhase[];
  }): SoloListrTask<AnyListrContext> {
    interface Context {
      config: {namespace: string; nodeAliases: NodeAliases};
    }

    return {
      title: 'Validate nodes states',
      skip: (): boolean => !this.remoteConfig.isLoaded(),
      task: (context_: Context, task): SoloListr<Context> => {
        const nodeAliases: NodeAliases = context_.config.nodeAliases;

        const subTasks: SoloListrTask<Context>[] = nodeAliases.map(
          (nodeAlias): SoloListrTask<AnyListrContext> => ({
            title: `Validating state for node ${nodeAlias}`,
            task: (_, task): void => {
              const state: DeploymentPhase = this.validateNodeState(
                nodeAlias,
                this.remoteConfig.configuration.components,
                acceptedPhases,
                excludedPhases,
              );

              task.title += ` - ${chalk.green('valid state')}: ${chalk.cyan(state)}`;
            },
          }),
        );

        return task.newListr(subTasks, {
          concurrent: false,
          rendererOptions: {collapseSubtasks: false},
        });
      },
    };
  }

  /**
   * Creates tasks to validate that specific node state is either one of the accepted states or not one of the excluded.
   *
   * @param acceptedPhases - the state at which the node can be, not matching any of the states throws an error
   * @param excludedPhases - the state at which the node can't be, matching any of the states throws an error
   */
  public validateSingleNodeState({
    acceptedPhases,
    excludedPhases,
  }: {
    acceptedPhases?: DeploymentPhase[];
    excludedPhases?: DeploymentPhase[];
  }): SoloListrTask<AnyListrContext> {
    void acceptedPhases;
    void excludedPhases;

    interface Context {
      config: {namespace: string; nodeAlias: NodeAlias};
    }

    return {
      title: 'Validate nodes state',
      skip: (): boolean => !this.remoteConfig.isLoaded(),
      task: (context_: Context, task): void => {
        const nodeAlias: NodeAlias = context_.config.nodeAlias;

        task.title += ` ${nodeAlias}`;

        // TODO: Disabled for now until the node's state mapping is completed
        // const components = this.remoteConfig.components;
        // const state = this.validateNodeState(nodeAlias, components, acceptedPhases, excludedPhases);
        // task.title += ` - ${chalk.green('valid state')}: ${chalk.cyan(state)}`;
      },
    };
  }

  /**
   * @param nodeAlias - the alias of the node whose state to validate
   * @param components - the component data wrapper
   * @param acceptedPhases - the state at which the node can be, not matching any of the states throws an error
   * @param excludedPhases - the state at which the node can't be, matching any of the states throws an error
   */
  private validateNodeState(
    nodeAlias: NodeAlias,
    components: ComponentsDataWrapperApi,
    acceptedPhases: Optional<DeploymentPhase[]>,
    excludedPhases: Optional<DeploymentPhase[]>,
  ): DeploymentPhase {
    void acceptedPhases;
    void excludedPhases;

    let nodeComponent: ConsensusNodeStateSchema;
    try {
      nodeComponent = components.getComponent<ConsensusNodeStateSchema>(
        ComponentTypes.ConsensusNode,
        Templates.renderComponentIdFromNodeAlias(nodeAlias),
      );
    } catch {
      throw new SoloError(`${nodeAlias} not found in remote config`);
    }
    // TODO: Enable once states have been mapped
    // if (acceptedPhases && !acceptedPhases.includes(nodeComponent.state)) {
    //   const errorMessageData =
    //     `accepted states: ${acceptedPhases.join(', ')}, ` + `current state: ${nodeComponent.state}`;
    //   throw new SoloError(`${nodeAlias} has invalid state - ` + errorMessageData);
    // }
    //
    // if (excludedPhases && excludedPhases.includes(nodeComponent.state)) {
    //   const errorMessageData =
    //     `excluded states: ${excludedPhases.join(', ')}, ` + `current state: ${nodeComponent.state}`;
    //   throw new SoloError(`${nodeAlias} has invalid state - ` + errorMessageData);
    // }
    return nodeComponent.metadata.phase;
  }

  public async collectJavaFlightRecorderLogs(argv: ArgvStruct): Promise<boolean> {
    argv = helpers.addFlagsToArgv(argv, NodeFlags.COLLECT_JFR_FLAGS);
    const leaseWrapper: LeaseWrapper = {lease: undefined};

    await this.commandAction(
      argv,
      [
        this.tasks.loadConfiguration(argv, leaseWrapper, this.leaseManager),
        this.tasks.initialize(argv, this.configs.collectJfrConfigBuilder.bind(this.configs), leaseWrapper.lease),
        this.tasks.downloadJavaFlightRecorderLogs(),
      ],
      constants.LISTR_DEFAULT_OPTIONS.DEFAULT,
      'Error collecting jfr recording from node',
      leaseWrapper.lease,
    );

    return true;
  }
}
