// 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 MirrorNodeCommand_1;
import { ListrInquirerPromptAdapter } from '@listr2/prompt-adapter-inquirer';
import { confirm as confirmPrompt } from '@inquirer/prompts';
import { IllegalArgumentError } from '../core/errors/illegal-argument-error.js';
import { SoloError } from '../core/errors/solo-error.js';
import { UserBreak } from '../core/errors/user-break.js';
import * as constants from '../core/constants.js';
import { BaseCommand } from './base.js';
import { Flags as flags } from './flags.js';
import { resolveNamespaceFromDeployment } from '../core/resolvers.js';
import * as helpers from '../core/helpers.js';
import { prepareValuesFiles, showVersionBanner } from '../core/helpers.js';
import { ListrLock } from '../core/lock/listr-lock.js';
import * as fs from 'node:fs';
import * as versions from '../../version.js';
import { INGRESS_CONTROLLER_VERSION } from '../../version.js';
import chalk from 'chalk';
import { PvcReference } from '../integration/kube/resources/pvc/pvc-reference.js';
import { PvcName } from '../integration/kube/resources/pvc/pvc-name.js';
import { KeyManager } from '../core/key-manager.js';
import { PathEx } from '../business/utils/path-ex.js';
import { inject, injectable } from 'tsyringe-neo';
import { InjectTokens } from '../core/dependency-injection/inject-tokens.js';
import { patchInject } from '../core/dependency-injection/container-helper.js';
import { ComponentTypes } from '../core/config/remote/enumerations/component-types.js';
import { SecretType } from '../integration/kube/resources/secret/secret-type.js';
import { Base64 } from 'js-base64';
import { SemanticVersion } from '../business/utils/semantic-version.js';
import { Templates } from '../core/templates.js';
import yaml from 'yaml';
import { DeploymentPhase } from '../data/schema/model/remote/deployment-phase.js';
import { PostgresSharedResource } from '../core/shared-resources/postgres.js';
import { SharedResourceManager } from '../core/shared-resources/shared-resource-manager.js';
import { MirrorNodeDeployedEvent } from '../core/events/event-types/mirror-node-deployed-event.js';
import { ImageReference } from '../business/utils/image-reference.js';
var MirrorNodeCommandType;
(function (MirrorNodeCommandType) {
    MirrorNodeCommandType["ADD"] = "add";
    MirrorNodeCommandType["UPGRADE"] = "upgrade";
    MirrorNodeCommandType["DESTROY"] = "destroy";
})(MirrorNodeCommandType || (MirrorNodeCommandType = {}));
let MirrorNodeCommand = class MirrorNodeCommand extends BaseCommand {
    static { MirrorNodeCommand_1 = this; }
    postgresSharedResource;
    sharedResourceManager;
    accountManager;
    eventBus;
    constructor(postgresSharedResource, sharedResourceManager, accountManager, eventBus) {
        super();
        this.postgresSharedResource = postgresSharedResource;
        this.sharedResourceManager = sharedResourceManager;
        this.accountManager = accountManager;
        this.eventBus = eventBus;
        this.accountManager = patchInject(accountManager, InjectTokens.AccountManager, this.constructor.name);
        this.postgresSharedResource = patchInject(postgresSharedResource, InjectTokens.PostgresSharedResource, this.constructor.name);
        this.sharedResourceManager = patchInject(sharedResourceManager, InjectTokens.SharedResourceManager, this.constructor.name);
    }
    static DEPLOY_CONFIGS_NAME = 'deployConfigs';
    static UPGRADE_CONFIGS_NAME = 'upgradeConfigs';
    static DEPLOY_FLAGS_LIST = {
        required: [flags.deployment],
        optional: [
            flags.cacheDir,
            flags.chartDirectory,
            flags.mirrorNodeChartDirectory,
            flags.clusterRef,
            flags.enableIngress,
            flags.ingressControllerValueFile,
            flags.mirrorStaticIp,
            flags.quiet,
            flags.valuesFile,
            flags.mirrorNodeVersion,
            flags.componentImage,
            flags.pinger,
            flags.useExternalDatabase,
            flags.operatorId,
            flags.operatorKey,
            flags.storageType,
            flags.storageReadAccessKey,
            flags.storageReadSecrets,
            flags.storageEndpoint,
            flags.storageBucket,
            flags.storageBucketPrefix,
            flags.storageBucketRegion,
            flags.externalDatabaseHost,
            flags.externalDatabaseOwnerUsername,
            flags.externalDatabaseOwnerPassword,
            flags.externalDatabaseReadonlyUsername,
            flags.externalDatabaseReadonlyPassword,
            flags.domainName,
            flags.forcePortForward,
            flags.externalAddress,
            flags.soloChartVersion,
            flags.forceBlockNodeIntegration, // Used to bypass version requirements for block node integration
            flags.parallelDeploy,
        ],
    };
    static UPGRADE_FLAGS_LIST = {
        required: [flags.deployment],
        optional: [
            flags.clusterRef,
            flags.cacheDir,
            flags.chartDirectory,
            flags.mirrorNodeChartDirectory,
            flags.enableIngress,
            flags.ingressControllerValueFile,
            flags.mirrorStaticIp,
            flags.quiet,
            flags.valuesFile,
            flags.mirrorNodeVersion,
            flags.componentImage,
            flags.pinger,
            flags.useExternalDatabase,
            flags.operatorId,
            flags.operatorKey,
            flags.storageType,
            flags.storageReadAccessKey,
            flags.storageReadSecrets,
            flags.storageEndpoint,
            flags.storageBucket,
            flags.storageBucketPrefix,
            flags.storageBucketRegion,
            flags.externalDatabaseHost,
            flags.externalDatabaseOwnerUsername,
            flags.externalDatabaseOwnerPassword,
            flags.externalDatabaseReadonlyUsername,
            flags.externalDatabaseReadonlyPassword,
            flags.domainName,
            flags.forcePortForward,
            flags.externalAddress,
            flags.id,
            flags.soloChartVersion,
            flags.forceBlockNodeIntegration, // Used to bypass version requirements for block node integration
        ],
    };
    static DESTROY_FLAGS_LIST = {
        required: [flags.deployment],
        optional: [flags.chartDirectory, flags.clusterRef, flags.force, flags.quiet, flags.devMode, flags.id],
    };
    prepareBlockNodeIntegrationValues(config) {
        const configuration = this.remoteConfig.configuration;
        const blockNodeSchemas = configuration.components.state.blockNodes;
        if (blockNodeSchemas.length === 0) {
            this.logger.debug('No block nodes found in remote config configuration');
            return '';
        }
        let shouldConfigureMirrorNodeToPullFromBlockNode;
        if (config.forceBlockNodeIntegration) {
            // Bypass following checks
            this.logger.warn('Force flag enabled, bypassing version checks for block node integration');
            shouldConfigureMirrorNodeToPullFromBlockNode = true;
        }
        else {
            const isConsensusNodeVersionSupported = this.remoteConfig.configuration.versions.consensusNode.greaterThanOrEqual(versions.MINIMUM_HIERO_PLATFORM_VERSION_FOR_TSS);
            const isBlockNodeChartVersionSupported = this.remoteConfig.configuration.versions.blockNodeChart.greaterThanOrEqual(versions.MINIMUM_BLOCK_NODE_CHART_VERSION_FOR_MIRROR_NODE_INTEGRATION);
            const isMirrorNodeVersionSupported = new SemanticVersion(config.mirrorNodeVersion).greaterThanOrEqual(versions.MINIMUM_MIRROR_NODE_CHART_VERSION_FOR_MIRROR_NODE_INTEGRATION);
            shouldConfigureMirrorNodeToPullFromBlockNode =
                isConsensusNodeVersionSupported && isBlockNodeChartVersionSupported && isMirrorNodeVersionSupported;
        }
        if (!shouldConfigureMirrorNodeToPullFromBlockNode) {
            this.logger.info('Mirror node will remain configured to pull from consensus node because version requirements were not met');
            return '';
        }
        const clusterSchemas = configuration.clusters;
        this.logger.debug('Preparing mirror node values args overrides for block nodes integration');
        const blockNodeFqdnList = [];
        for (const blockNode of blockNodeSchemas) {
            const id = blockNode.metadata.id;
            const clusterReference = blockNode.metadata.cluster;
            const cluster = clusterSchemas.find((cluster) => cluster.name === clusterReference);
            if (!cluster) {
                throw new SoloError(`Cluster ${clusterReference} not found in remote config`);
            }
            const serviceName = Templates.renderBlockNodeName(id);
            const namespace = blockNode.metadata.namespace;
            const dnsBaseDomain = cluster.dnsBaseDomain;
            const fqdn = Templates.renderSvcFullyQualifiedDomainName(serviceName, namespace, dnsBaseDomain);
            blockNodeFqdnList.push({
                host: fqdn,
                port: constants.BLOCK_NODE_PORT,
            });
        }
        const data = {
            SPRING_PROFILES_ACTIVE: 'blocknode',
        };
        for (const [index, node] of blockNodeFqdnList.entries()) {
            data[`HIERO_MIRROR_IMPORTER_BLOCK_NODES_${index}_HOST`] = node.host;
            if (node.port !== constants.BLOCK_NODE_PORT) {
                data[`HIERO_MIRROR_IMPORTER_BLOCK_NODES_${index}_PORT`] = node.port;
            }
        }
        const mirrorNodeBlockNodeValues = {
            importer: {
                env: data,
            },
        };
        const mirrorNodeBlockNodeValuesYaml = yaml.stringify(mirrorNodeBlockNodeValues);
        const valuesFilePath = PathEx.join(config.cacheDir, 'mirror-bn-values.yaml');
        fs.writeFileSync(valuesFilePath, mirrorNodeBlockNodeValuesYaml);
        return ` --values ${valuesFilePath}`;
    }
    async prepareValuesArg(config) {
        let valuesArgument = '';
        valuesArgument += ' --install';
        if (config.valuesFile) {
            valuesArgument += helpers.prepareValuesFiles(config.valuesFile);
        }
        config.mirrorNodeVersion = SemanticVersion.getValidSemanticVersion(config.mirrorNodeVersion, true, 'Mirror node version');
        const chartNamespace = this.getChartNamespace(config.mirrorNodeVersion);
        const environmentVariablePrefix = this.getEnvironmentVariablePrefix(config.mirrorNodeVersion);
        if (config.componentImage) {
            const parsedImageReference = ImageReference.parseImageReference(config.componentImage);
            valuesArgument += helpers.populateHelmArguments({
                'importer.image.registry': parsedImageReference.registry,
                'grpc.image.registry': parsedImageReference.registry,
                'rest.image.registry': parsedImageReference.registry,
                'restjava.image.registry': parsedImageReference.registry,
                'web3.image.registry': parsedImageReference.registry,
                'monitor.image.registry': parsedImageReference.registry,
                'importer.image.repository': parsedImageReference.repository,
                'grpc.image.repository': parsedImageReference.repository,
                'rest.image.repository': parsedImageReference.repository,
                'restjava.image.repository': parsedImageReference.repository,
                'web3.image.repository': parsedImageReference.repository,
                'monitor.image.repository': parsedImageReference.repository,
                'importer.image.tag': parsedImageReference.tag,
                'grpc.image.tag': parsedImageReference.tag,
                'rest.image.tag': parsedImageReference.tag,
                'restjava.image.tag': parsedImageReference.tag,
                'web3.image.tag': parsedImageReference.tag,
                'monitor.image.tag': parsedImageReference.tag,
            });
        }
        if (config.storageBucket) {
            valuesArgument += ` --set importer.config.${chartNamespace}.mirror.importer.downloader.bucketName=${config.storageBucket}`;
        }
        if (config.storageBucketPrefix) {
            this.logger.info(`Setting storage bucket prefix to ${config.storageBucketPrefix}`);
            valuesArgument += ` --set importer.config.${chartNamespace}.mirror.importer.downloader.pathPrefix=${config.storageBucketPrefix}`;
        }
        let storageType = '';
        if (config.storageType !== constants.StorageType.MINIO_ONLY &&
            config.storageReadAccessKey &&
            config.storageReadSecrets &&
            config.storageEndpoint) {
            if (config.storageType === constants.StorageType.GCS_ONLY ||
                config.storageType === constants.StorageType.AWS_AND_GCS) {
                storageType = 'gcp';
            }
            else if (config.storageType === constants.StorageType.AWS_ONLY) {
                storageType = 's3';
            }
            else {
                throw new IllegalArgumentError(`Invalid cloud storage type: ${config.storageType}`);
            }
            const mapping = {
                [`importer.env.${environmentVariablePrefix}_MIRROR_IMPORTER_DOWNLOADER_CLOUDPROVIDER`]: storageType,
                [`importer.env.${environmentVariablePrefix}_MIRROR_IMPORTER_DOWNLOADER_ENDPOINTOVERRIDE`]: config.storageEndpoint,
                [`importer.env.${environmentVariablePrefix}_MIRROR_IMPORTER_DOWNLOADER_ACCESSKEY`]: config.storageReadAccessKey,
                [`importer.env.${environmentVariablePrefix}_MIRROR_IMPORTER_DOWNLOADER_SECRETKEY`]: config.storageReadSecrets,
            };
            valuesArgument += helpers.populateHelmArguments(mapping);
        }
        if (config.storageBucketRegion) {
            valuesArgument += ` --set importer.env.${environmentVariablePrefix}_MIRROR_IMPORTER_DOWNLOADER_REGION=${config.storageBucketRegion}`;
        }
        if (config.domainName) {
            valuesArgument += helpers.populateHelmArguments({
                'ingress.enabled': true,
                'ingress.tls.enabled': false,
                'ingress.hosts[0].host': config.domainName,
            });
        }
        // if the useExternalDatabase populate all the required values before installing the chart
        let host, ownerPassword, ownerUsername, readonlyPassword, readonlyUsername;
        valuesArgument += helpers.populateHelmArguments({
            // Disable default database deployment
            'stackgres.enabled': false,
            'postgresql.enabled': false,
            'db.name': 'mirror_node',
        });
        if (config.useExternalDatabase) {
            host = config.externalDatabaseHost;
            ownerPassword = config.externalDatabaseOwnerPassword;
            ownerUsername = config.externalDatabaseOwnerUsername;
            readonlyUsername = config.externalDatabaseReadonlyUsername;
            readonlyPassword = config.externalDatabaseReadonlyPassword;
            valuesArgument += helpers.populateHelmArguments({
                // Set the host and name
                'db.host': host,
                // set the usernames
                'db.owner.username': ownerUsername,
                'importer.db.username': ownerUsername,
                'grpc.db.username': readonlyUsername,
                'restjava.db.username': readonlyUsername,
                'web3.db.username': readonlyUsername,
                // TODO: Fixes a problem where importer's V1.0__Init.sql migration fails
                // 'rest.db.username': readonlyUsername,
                // set the passwords
                'db.owner.password': ownerPassword,
                'importer.db.password': ownerPassword,
                'grpc.db.password': readonlyPassword,
                'restjava.db.password': readonlyPassword,
                'web3.db.password': readonlyPassword,
                'rest.db.password': readonlyPassword,
            });
        }
        else {
            valuesArgument += helpers.populateHelmArguments({
                'db.host': `solo-shared-resources-postgres.${config.namespace.name}.svc.cluster.local`,
            });
        }
        valuesArgument += this.prepareBlockNodeIntegrationValues(config);
        return valuesArgument;
    }
    async deployMirrorNode({ config }, commandType) {
        if (config.isChartInstalled &&
            new SemanticVersion(config.mirrorNodeVersion).greaterThanOrEqual(versions.POST_HIERO_MIGRATION_MIRROR_NODE_VERSION)) {
            // migrating mirror node passwords from HEDERA_ (version 0.129.0) to HIERO_
            const existingSecrets = await this.k8Factory
                .getK8(config.clusterContext)
                .secrets()
                .read(config.namespace, 'mirror-passwords');
            const updatedData = {};
            for (const [key, value] of Object.entries(existingSecrets.data)) {
                if (key.startsWith('HEDERA_')) {
                    updatedData[key.replace('HEDERA_', 'HIERO_')] = value;
                }
                else {
                    updatedData[key] = value;
                }
            }
            if (Object.keys(updatedData).length > 0) {
                await this.k8Factory
                    .getK8(config.clusterContext)
                    .secrets()
                    .replace(config.namespace, 'mirror-passwords', SecretType.OPAQUE, updatedData);
            }
        }
        // Determine if we should reuse values based on the currently deployed version from remote config
        // If upgrading from a version <= MIRROR_NODE_VERSION_BOUNDARY, we need to skip reuseValues
        // to avoid RegularExpression rules from old version causing relay node request failures
        const currentVersion = this.remoteConfig.getComponentVersion(ComponentTypes.MirrorNode);
        let shouldReuseValues = currentVersion
            ? currentVersion.greaterThan(constants.MIRROR_NODE_VERSION_BOUNDARY)
            : false; // If no current version (first install), don't reuse values
        // Don't reuse values when crossing the shared-resources/memory-improvements boundary
        // (upgrading from < v0.152.0 → >= v0.152.0).  Versions before this boundary used an
        // embedded chart-managed Redis with sentinel nodes pointed at "<release>-redis".
        // Reusing those old values would leak the stale "SPRING_DATA_REDIS_SENTINEL_NODES"
        // configuration into the upgraded pods even though redis.enabled is now set to false,
        // because --reuse-values merges ALL old chart values (including sentinel node addresses)
        // and we only explicitly override redis.enabled / redis.host / redis.port — not every
        // sentinel sub-key.  Forcing a clean value set here prevents pods from failing to
        // resolve the no-longer-existent "<release>-redis" hostname.
        if (shouldReuseValues &&
            currentVersion !== null &&
            currentVersion.lessThan(versions.MEMORY_ENHANCEMENTS_MIRROR_NODE_VERSION) &&
            new SemanticVersion(config.mirrorNodeVersion).greaterThanOrEqual(versions.MEMORY_ENHANCEMENTS_MIRROR_NODE_VERSION)) {
            shouldReuseValues = false;
        }
        if (commandType === MirrorNodeCommandType.ADD) {
            shouldReuseValues = false;
        }
        await this.chartManager.upgrade(config.namespace, config.releaseName, constants.MIRROR_NODE_CHART, config.mirrorNodeChartDirectory || constants.MIRROR_NODE_RELEASE_NAME, config.mirrorNodeVersion, config.valuesArg, config.clusterContext, shouldReuseValues);
        this.eventBus.emit(new MirrorNodeDeployedEvent(config.deployment));
        showVersionBanner(this.logger, constants.MIRROR_NODE_RELEASE_NAME, config.mirrorNodeVersion);
        if (commandType === MirrorNodeCommandType.ADD) {
            this.remoteConfig.configuration.components.changeComponentPhase(config.newMirrorNodeComponent.metadata.id, ComponentTypes.MirrorNode, DeploymentPhase.DEPLOYED);
            await this.remoteConfig.persist();
        }
        else if (commandType === MirrorNodeCommandType.UPGRADE) {
            // update mirror node version in remote config after successful upgrade
            this.remoteConfig.updateComponentVersion(ComponentTypes.MirrorNode, new SemanticVersion(config.mirrorNodeVersion));
            await this.remoteConfig.persist();
        }
        if (config.enableIngress) {
            const existingIngressClasses = await this.k8Factory
                .getK8(config.clusterContext)
                .ingressClasses()
                .list();
            for (const ingressClass of existingIngressClasses) {
                this.logger.debug(`Found existing IngressClass [${ingressClass.name}]`);
                if (ingressClass.name === constants.MIRROR_INGRESS_CLASS_NAME) {
                    this.logger.showUser(`${constants.MIRROR_INGRESS_CLASS_NAME} already found, skipping`);
                    return;
                }
            }
            await KeyManager.createTlsSecret(this.k8Factory, config.namespace, config.domainName, config.cacheDir, constants.MIRROR_INGRESS_TLS_SECRET_NAME);
            // patch ingressClassName of mirror ingress, so it can be recognized by haproxy ingress controller
            const updated = {
                metadata: {
                    annotations: {
                        'haproxy-ingress.github.io/path-type': 'regex',
                    },
                },
                spec: {
                    ingressClassName: `${constants.MIRROR_INGRESS_CLASS_NAME}`,
                    tls: [
                        {
                            hosts: [config.domainName || 'localhost'],
                            secretName: constants.MIRROR_INGRESS_TLS_SECRET_NAME,
                        },
                    ],
                },
            };
            await this.k8Factory
                .getK8(config.clusterContext)
                .ingresses()
                .update(config.namespace, constants.MIRROR_NODE_RELEASE_NAME, updated);
            await this.k8Factory
                .getK8(config.clusterContext)
                .ingressClasses()
                .create(constants.MIRROR_INGRESS_CLASS_NAME, constants.INGRESS_CONTROLLER_PREFIX + constants.MIRROR_INGRESS_CONTROLLER);
        }
    }
    getReleaseName() {
        return this.renderReleaseName(this.remoteConfig.configuration.components.getNewComponentId(ComponentTypes.MirrorNode));
    }
    getIngressReleaseName() {
        return this.renderIngressReleaseName(this.remoteConfig.configuration.components.getNewComponentId(ComponentTypes.MirrorNode));
    }
    renderReleaseName(id) {
        if (typeof id !== 'number') {
            throw new SoloError(`Invalid component id: ${id}, type: ${typeof id}`);
        }
        return `${constants.MIRROR_NODE_RELEASE_NAME}-${id}`;
    }
    renderIngressReleaseName(id) {
        if (typeof id !== 'number') {
            throw new SoloError(`Invalid component id: ${id}, type: ${typeof id}`);
        }
        return `${constants.INGRESS_CONTROLLER_RELEASE_NAME}-${id}`;
    }
    enableSharedResourcesTask() {
        return {
            title: 'Enable shared resources',
            task: async (_, task) => {
                const subTasks = [
                    {
                        title: 'Install Shared Resources chart',
                        task: async (context_) => {
                            if (!context_.config.useExternalDatabase) {
                                this.sharedResourceManager.enablePostgres();
                            }
                            this.sharedResourceManager.enableRedis();
                            context_.config.installSharedResources = await this.sharedResourceManager.installChart(context_.config.namespace, context_.config.chartDirectory, context_.config.soloChartVersion, context_.config.clusterContext, {
                                'redis.image.registry': constants.REDIS_IMAGE_REGISTRY,
                                'redis.image.repository': constants.REDIS_IMAGE_REPOSITORY,
                                'redis.image.tag': versions.REDIS_IMAGE_VERSION,
                                'redis.sentinel.image.registry': constants.REDIS_SENTINEL_IMAGE_REGISTRY,
                                'redis.sentinel.image.repository': constants.REDIS_SENTINEL_IMAGE_REPOSITORY,
                                'redis.sentinel.image.tag': versions.REDIS_SENTINEL_IMAGE_VERSION,
                                'redis.sentinel.masterSet': constants.REDIS_SENTINEL_MASTER_SET,
                            });
                        },
                    },
                    {
                        title: 'Load redis credentials',
                        task: async (context_) => {
                            const secrets = await this.k8Factory
                                .getK8(context_.config.clusterContext)
                                .secrets()
                                .list(context_.config.namespace, ['app.kubernetes.io/instance=solo-shared-resources']);
                            const secret = secrets.find((secret) => secret.name === 'solo-shared-resources-redis');
                            // Update values
                            context_.config.valuesArg += helpers.populateHelmArguments({
                                'redis.enabled': false,
                                'redis.auth.password': Base64.decode(secret.data['SPRING_DATA_REDIS_PASSWORD']),
                                'redis.host': Base64.decode(secret.data['SPRING_DATA_REDIS_HOST']),
                                'redis.port': Base64.decode(secret.data['SPRING_DATA_REDIS_PORT']),
                            });
                        },
                    },
                    {
                        title: 'Initialize Postgres pod',
                        task: (context_, task) => {
                            const subTasks = [
                                {
                                    title: 'Wait for Postgres pod to be ready',
                                    task: async (context_) => {
                                        await this.postgresSharedResource.waitForPodReady(context_.config.namespace, context_.config.clusterContext);
                                    },
                                },
                            ];
                            // set up the sub-tasks
                            return task.newListr(subTasks, {
                                concurrent: false, // no need to run concurrently since if one node is up, the rest should be up by then
                                rendererOptions: {
                                    collapseSubtasks: false,
                                },
                            });
                        },
                        skip: (context_) => context_.config.useExternalDatabase,
                    },
                    {
                        title: 'Add shared resource components to remote config',
                        skip: (context_) => !context_.config.installSharedResources || !this.remoteConfig.isLoaded(),
                        task: async (context_) => {
                            if (!context_.config.useExternalDatabase) {
                                const postgresComponent = this.componentFactory.createNewPostgresComponent(context_.config.clusterReference, context_.config.namespace);
                                this.remoteConfig.configuration.components.addNewComponent(postgresComponent, ComponentTypes.Postgres);
                            }
                            const redisComponent = this.componentFactory.createNewRedisComponent(context_.config.clusterReference, context_.config.namespace);
                            this.remoteConfig.configuration.components.addNewComponent(redisComponent, ComponentTypes.Redis);
                            await this.remoteConfig.persist();
                        },
                    },
                ];
                // set up the sub-tasks
                return task.newListr(subTasks, {
                    concurrent: false, // no need to run concurrently since if one node is up, the rest should be up by then
                    rendererOptions: {
                        collapseSubtasks: false,
                    },
                });
            },
        };
    }
    initializeSharedPostgresDatabaseTask() {
        return {
            title: 'Run database initialization script',
            task: async (context_) => {
                await this.postgresSharedResource.initializeMirrorNode(context_.config.namespace, context_.config.clusterContext, this.getEnvironmentVariablePrefix(context_.config.mirrorNodeVersion));
            },
            skip: ({ config }) => config.useExternalDatabase || !config.installSharedResources,
        };
    }
    /**
     * Installs the mirror chart with all application components disabled in order to create the
     * `mirror-passwords` secret.  The init script (run by {@link initializeSharedPostgresDatabaseTask})
     * reads that secret to obtain the DB user passwords, so the secret must exist before init runs.
     * The importer must not be running during init (it would hold a session that blocks DROP DATABASE),
     * so we use this lightweight prime install instead of a full chart install.
     *
     * Skipped when the secret already exists (upgrade path) or when using an external database.
     */
    /**
     * Deletes the `<release>-redis` secret so that the subsequent mirror chart install/upgrade
     * re-creates it cleanly.  This is necessary because Kubernetes strategic-merge-patch does not
     * remove keys — stale `SPRING_DATA_REDIS_SENTINEL_NODES` values written by a previous install
     * (using the internal chart-managed Redis) would otherwise persist and cause pods to try to
     * resolve a non-existent hostname.
     */
    deleteStaleRedisSecretTask() {
        return {
            title: 'Delete stale mirror redis secret',
            task: async (context_) => {
                // secrets().delete() returns true for NotFound, so no try/catch needed.
                await this.k8Factory
                    .getK8(context_.config.clusterContext)
                    .secrets()
                    .delete(context_.config.namespace, `${context_.config.releaseName}-redis`);
            },
        };
    }
    primePostgresSecretTask() {
        return {
            title: 'Prime mirror-node postgres secret',
            task: async (context_) => {
                // Skip if the secret was already created by a previous install.
                const secretExists = await this.k8Factory
                    .getK8(context_.config.clusterContext)
                    .secrets()
                    .exists(context_.config.namespace, 'mirror-passwords');
                if (secretExists) {
                    return;
                }
                // Install the mirror chart with every application component disabled.  This is enough for
                // Helm to render and apply the `mirror-passwords` Secret template without starting any pods
                // that could connect to Postgres before the init script runs.
                //
                // redis.enabled must be false here: when true the chart writes SPRING_DATA_REDIS_SENTINEL_NODES
                // into the <release>-redis secret using the chart default host ({{ .Release.Name }}-redis).
                // Kubernetes strategic-merge-patch does not remove keys, so those stale sentinel values would
                // persist through the full upgrade (which sets redis.enabled=false and skips the sentinel block).
                // Setting redis.enabled=false in the prime install prevents the stale keys from ever being written.
                const primeValuesArgument = ' --install' +
                    helpers.populateHelmArguments({
                        'stackgres.enabled': false,
                        'postgresql.enabled': false,
                        'redis.enabled': false,
                        'db.host': `solo-shared-resources-postgres.${context_.config.namespace.name}.svc.cluster.local`,
                        'db.name': 'mirror_node',
                        'importer.enabled': false,
                        'grpc.enabled': false,
                        'rest.enabled': false,
                        'restjava.enabled': false,
                        'web3.enabled': false,
                        'rosetta.enabled': false,
                        'graphql.enabled': false,
                        'monitor.enabled': false,
                    });
                await this.chartManager.upgrade(context_.config.namespace, context_.config.releaseName, constants.MIRROR_NODE_CHART, context_.config.mirrorNodeChartDirectory || constants.MIRROR_NODE_RELEASE_NAME, context_.config.mirrorNodeVersion, primeValuesArgument, context_.config.clusterContext, false);
            },
            skip: ({ config }) => config.useExternalDatabase || !config.installSharedResources,
        };
    }
    enableMirrorNodeTask(commandType) {
        return {
            title: 'Enable mirror-node',
            task: (_, parentTask) => parentTask.newListr([
                {
                    title: 'Prepare address book',
                    task: async (context_) => {
                        if (this.oneShotState.isActive()) {
                            context_.addressBook = await this.accountManager.buildAddressBookBase64(PathEx.join(context_.config.cacheDir, 'keys'), context_.config.deployment);
                            context_.config.valuesArg += ` --set "importer.addressBook=${context_.addressBook}"`;
                        }
                        else {
                            const deployment = this.configManager.getFlag(flags.deployment);
                            const portForward = this.configManager.getFlag(flags.forcePortForward);
                            context_.addressBook = await this.accountManager.prepareAddressBookBase64(context_.config.namespace, this.remoteConfig.getClusterRefs(), deployment, this.configManager.getFlag(flags.operatorId), this.configManager.getFlag(flags.operatorKey), portForward);
                            context_.config.valuesArg += ` --set "importer.addressBook=${context_.addressBook}"`;
                        }
                    },
                },
                {
                    title: 'Install mirror ingress controller',
                    task: async (context_) => {
                        const config = context_.config;
                        let mirrorIngressControllerValuesArgument = ' --install ';
                        mirrorIngressControllerValuesArgument += helpers.prepareValuesFiles(constants.INGRESS_CONTROLLER_VALUES_FILE);
                        if (config.mirrorStaticIp !== '') {
                            mirrorIngressControllerValuesArgument += ` --set controller.service.loadBalancerIP=${context_.config.mirrorStaticIp}`;
                        }
                        mirrorIngressControllerValuesArgument += ` --set fullnameOverride=${constants.MIRROR_INGRESS_CONTROLLER}-${config.namespace.name}`;
                        mirrorIngressControllerValuesArgument += ` --set controller.ingressClass=${constants.MIRROR_INGRESS_CLASS_NAME}`;
                        mirrorIngressControllerValuesArgument += ` --set controller.extraArgs.controller-class=${constants.MIRROR_INGRESS_CONTROLLER}`;
                        mirrorIngressControllerValuesArgument += prepareValuesFiles(config.ingressControllerValueFile);
                        await this.chartManager.upgrade(config.namespace, config.ingressReleaseName, constants.INGRESS_CONTROLLER_RELEASE_NAME, constants.INGRESS_CONTROLLER_RELEASE_NAME, INGRESS_CONTROLLER_VERSION, mirrorIngressControllerValuesArgument, context_.config.clusterContext);
                        await this.adoptMirrorIngressControllerRbacOwnership(config);
                        showVersionBanner(this.logger, config.ingressReleaseName, INGRESS_CONTROLLER_VERSION);
                    },
                    skip: (context_) => !context_.config.enableIngress,
                },
                {
                    title: 'Deploy mirror-node',
                    task: async (context_) => {
                        await this.deployMirrorNode(context_, commandType);
                    },
                },
            ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT),
        };
    }
    checkPodsAreReadyNodeTask() {
        return {
            title: 'Check pods are ready',
            task: async (context_, task) => {
                const instanceCandidates = [
                    this.renderReleaseName(context_.config.id), // e.g. mirror-1
                    context_.config.releaseName,
                ];
                if (context_.config.id === 1) {
                    instanceCandidates.push(constants.MIRROR_NODE_RELEASE_NAME); // legacy release name
                }
                const podsInAllNamespaces = [];
                for (const instanceName of new Set(instanceCandidates)) {
                    const candidatePods = await this.k8Factory
                        .getK8(context_.config.clusterContext)
                        .pods()
                        .listForAllNamespaces([`app.kubernetes.io/instance=${instanceName}`]);
                    podsInAllNamespaces.push(...candidatePods);
                }
                const podsClient = this.k8Factory.getK8(context_.config.clusterContext).pods();
                const namespacePodReferences = [
                    ...new Map(podsInAllNamespaces
                        .filter((pod) => pod.podReference?.namespace?.name === context_.config.namespace.name)
                        .map((pod) => [
                        `${pod.podReference.namespace.name}/${pod.podReference.name.name}`,
                        pod.podReference,
                    ])).values(),
                ];
                const namespacePods = await Promise.all(namespacePodReferences.map(async (podReference) => await podsClient.read(podReference)));
                const deployedPods = namespacePods.filter((pod) => !!pod.labels?.['app.kubernetes.io/component'] && !!pod.labels?.['app.kubernetes.io/name']);
                if (deployedPods.length === 0) {
                    throw new SoloError(`No deployed mirror-node pods found for release ${context_.config.releaseName} in namespace ${context_.config.namespace.name}`);
                }
                const checksBySelector = new Map();
                for (const pod of deployedPods) {
                    const component = pod.labels?.['app.kubernetes.io/component'];
                    const name = pod.labels?.['app.kubernetes.io/name'];
                    const key = `${component}|${name}`;
                    if (!checksBySelector.has(key)) {
                        const titleName = component
                            .split('-')
                            .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
                            .join(' ');
                        checksBySelector.set(key, {
                            title: `Check ${titleName}`,
                            labels: [
                                `app.kubernetes.io/component=${component}`,
                                `app.kubernetes.io/name=${name}`,
                                `app.kubernetes.io/instance=${pod.labels?.['app.kubernetes.io/instance']}`,
                            ],
                        });
                    }
                }
                const subTasks = [
                    ...checksBySelector.values(),
                ].map(({ title, labels, }) => ({
                    title,
                    task: async () => await this.k8Factory
                        .getK8(context_.config.clusterContext)
                        .pods()
                        .waitForReadyStatus(context_.config.namespace, labels, constants.PODS_READY_MAX_ATTEMPTS, constants.PODS_READY_DELAY),
                }));
                return task.newListr(subTasks, constants.LISTR_DEFAULT_OPTIONS.WITH_CONCURRENCY);
            },
        };
    }
    enablePortForwardingTask() {
        return {
            title: 'Enable port forwarding for mirror ingress controller',
            skip: ({ config }) => !config.forcePortForward || !config.enableIngress,
            task: async ({ config }) => {
                const externalAddress = this.configManager.getFlag(flags.externalAddress);
                const pods = await this.k8Factory
                    .getK8(config.clusterContext)
                    .pods()
                    .list(config.namespace, [`app.kubernetes.io/instance=${config.ingressReleaseName}`]);
                if (pods.length === 0) {
                    throw new SoloError('No mirror ingress controller pod found');
                }
                let podReference;
                for (const pod of pods) {
                    if (pod?.podReference?.name?.name?.startsWith('mirror-ingress')) {
                        podReference = pod.podReference;
                        break;
                    }
                }
                await this.remoteConfig.configuration.components.managePortForward(config.clusterReference, podReference, 80, // Pod port
                constants.MIRROR_NODE_PORT, // Local port
                this.k8Factory.getK8(config.clusterContext), this.logger, ComponentTypes.MirrorNode, 'Mirror ingress controller', config.isChartInstalled, // Reuse existing port if chart is already installed
                undefined, true, // persist: auto-restart on failure using persist-port-forward.js
                externalAddress);
                await this.remoteConfig.persist();
            },
        };
    }
    async add(argv) {
        let lease;
        const tasks = this.taskList.newTaskList([
            {
                title: 'Initialize',
                task: async (context_, task) => {
                    await this.localConfig.load();
                    await this.loadRemoteConfigOrWarn(argv);
                    if (!this.oneShotState.isActive()) {
                        lease = await this.leaseManager.create();
                    }
                    this.configManager.update(argv);
                    flags.disablePrompts(MirrorNodeCommand_1.DEPLOY_FLAGS_LIST.optional);
                    const allFlags = [
                        ...MirrorNodeCommand_1.DEPLOY_FLAGS_LIST.required,
                        ...MirrorNodeCommand_1.DEPLOY_FLAGS_LIST.optional,
                    ];
                    await this.configManager.executePrompt(task, allFlags);
                    const config = this.configManager.getConfig(MirrorNodeCommand_1.DEPLOY_CONFIGS_NAME, allFlags, []);
                    context_.config = config;
                    const hasMirrorNodeMemoryImprovements = new SemanticVersion(config.mirrorNodeVersion).greaterThanOrEqual(versions.MEMORY_ENHANCEMENTS_MIRROR_NODE_VERSION);
                    config.namespace = await this.getNamespace(task);
                    config.clusterReference = this.getClusterReference();
                    config.clusterContext = this.getClusterContext(config.clusterReference);
                    config.newMirrorNodeComponent = this.componentFactory.createNewMirrorNodeComponent(config.clusterReference, config.namespace);
                    config.newMirrorNodeComponent.metadata.phase = DeploymentPhase.REQUESTED;
                    config.id = config.newMirrorNodeComponent.metadata.id;
                    config.installSharedResources = false;
                    const useMirrorNodeLegacyReleaseName = process.env.USE_MIRROR_NODE_LEGACY_RELEASE_NAME === 'true';
                    if (useMirrorNodeLegacyReleaseName) {
                        config.releaseName = constants.MIRROR_NODE_RELEASE_NAME;
                        config.ingressReleaseName = `${constants.INGRESS_CONTROLLER_RELEASE_NAME}-${config.namespace.name}`;
                    }
                    else {
                        config.releaseName = this.getReleaseName();
                        config.ingressReleaseName = this.getIngressReleaseName();
                    }
                    config.isChartInstalled = await this.chartManager.isChartInstalled(config.namespace, config.releaseName, config.clusterContext);
                    context_.config.soloChartVersion = SemanticVersion.getValidSemanticVersion(context_.config.soloChartVersion, false, 'Solo chart version');
                    // predefined values first
                    config.valuesArg = helpers.prepareValuesFiles(new SemanticVersion(config.mirrorNodeVersion).lessThan(versions.POST_HIERO_MIGRATION_MIRROR_NODE_VERSION)
                        ? constants.MIRROR_NODE_VALUES_FILE_HEDERA
                        : constants.MIRROR_NODE_VALUES_FILE);
                    // user defined values later to override predefined values
                    config.valuesArg += await this.prepareValuesArg(config);
                    config.deployment = this.configManager.getFlag(flags.deployment);
                    const realm = this.localConfig.configuration.realmForDeployment(config.deployment);
                    const shard = this.localConfig.configuration.shardForDeployment(config.deployment);
                    const chartNamespace = this.getChartNamespace(config.mirrorNodeVersion);
                    const modules = ['monitor', 'rest', 'grpc', 'importer', 'restjava', 'graphql', 'rosetta', 'web3'];
                    for (const module of modules) {
                        config.valuesArg += ` --set ${module}.config.${chartNamespace}.mirror.common.realm=${realm}`;
                        config.valuesArg += ` --set ${module}.config.${chartNamespace}.mirror.common.shard=${shard}`;
                    }
                    if (config.pinger) {
                        if (!hasMirrorNodeMemoryImprovements) {
                            config.valuesArg += ' --set pinger.enabled=false';
                            config.valuesArg += ' --set monitor.enabled=true';
                            config.valuesArg += ` --set monitor.config.${chartNamespace}.mirror.monitor.publish.scenarios.pinger.tps=${constants.MIRROR_NODE_PINGER_TPS}`;
                        }
                        const operatorId = config.operatorId || this.accountManager.getOperatorAccountId(config.deployment).toString();
                        config.valuesArg += ` --set monitor.config.${chartNamespace}.mirror.monitor.operator.accountId=${operatorId}`;
                        config.valuesArg += ` --set pinger.env.HIERO_MIRROR_PINGER_OPERATOR_ID=${operatorId}`;
                        if (config.operatorKey) {
                            this.logger.info('Using provided operator key');
                            config.valuesArg += ` --set monitor.config.${chartNamespace}.mirror.monitor.operator.privateKey=${config.operatorKey}`;
                            config.valuesArg += ` --set pinger.env.HIERO_MIRROR_PINGER_OPERATOR_KEY=${config.operatorKey}`;
                        }
                        else {
                            try {
                                const namespace = await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task);
                                const secrets = await this.k8Factory
                                    .getK8(config.clusterContext)
                                    .secrets()
                                    .list(namespace, [`solo.hedera.com/account-id=${operatorId}`]);
                                if (secrets.length === 0) {
                                    this.logger.info(`No k8s secret found for operator account id ${operatorId}, use default one`);
                                    config.valuesArg += ` --set monitor.config.${chartNamespace}.mirror.monitor.operator.privateKey=${constants.OPERATOR_KEY}`;
                                    config.valuesArg += ` --set pinger.env.HIERO_MIRROR_PINGER_OPERATOR_KEY=${constants.OPERATOR_KEY}`;
                                }
                                else {
                                    this.logger.info('Using operator key from k8s secret');
                                    const operatorKeyFromK8 = Base64.decode(secrets[0].data.privateKey);
                                    config.valuesArg += ` --set monitor.config.${chartNamespace}.mirror.monitor.operator.privateKey=${operatorKeyFromK8}`;
                                    config.valuesArg += ` --set pinger.env.HIERO_MIRROR_PINGER_OPERATOR_KEY=${operatorKeyFromK8}`;
                                }
                            }
                            catch (error) {
                                throw new SoloError(`Error getting operator key: ${error.message}`, error);
                            }
                        }
                    }
                    else {
                        context_.config.valuesArg += ' --set monitor.enabled=false';
                        context_.config.valuesArg += ' --set pinger.enabled=false';
                    }
                    const isQuiet = config.quiet;
                    // In case the useExternalDatabase is set, prompt for the rest of the required data
                    if (config.useExternalDatabase && !isQuiet) {
                        await this.configManager.executePrompt(task, [
                            flags.externalDatabaseHost,
                            flags.externalDatabaseOwnerUsername,
                            flags.externalDatabaseOwnerPassword,
                            flags.externalDatabaseReadonlyUsername,
                            flags.externalDatabaseReadonlyPassword,
                        ]);
                    }
                    else if (config.useExternalDatabase &&
                        (!config.externalDatabaseHost ||
                            !config.externalDatabaseOwnerUsername ||
                            !config.externalDatabaseOwnerPassword ||
                            !config.externalDatabaseReadonlyUsername ||
                            !config.externalDatabaseReadonlyPassword)) {
                        this.validateExternalDatabaseFlags(config);
                    }
                    await this.throwIfNamespaceIsMissing(config.clusterContext, config.namespace);
                    this.addMirrorNodeMemoryOverrides(hasMirrorNodeMemoryImprovements, config);
                    const lockTask = this.oneShotState.isActive()
                        ? ListrLock.newSkippedLockTask(task)
                        : ListrLock.newAcquireLockTask(lease, task);
                    return lockTask;
                },
            },
            this.addMirrorNodeComponents(),
            {
                title: 'load node client',
                task: async ({ config }) => {
                    await this.accountManager.loadNodeClient(config.namespace, this.remoteConfig.getClusterRefs(), config.deployment, this.configManager.getFlag(flags.forcePortForward));
                },
                skip: this.oneShotState.isActive(),
            },
            {
                title: 'Deploy charts',
                task: (_, parentTask) => {
                    const subTasks = [
                        this.enableSharedResourcesTask(),
                        this.primePostgresSecretTask(), // creates mirror-passwords secret before init reads it
                        this.deleteStaleRedisSecretTask(), // remove stale sentinel nodes left by a prior prime install
                        this.initializeSharedPostgresDatabaseTask(), // must run before mirror chart so importer doesn't hold a session during DB creation
                        this.enableMirrorNodeTask(MirrorNodeCommandType.ADD),
                    ];
                    return parentTask.newListr(subTasks, {
                        concurrent: false, // shared resources must be configured and DB initialized before mirror chart is installed
                        rendererOptions: {
                            collapseSubtasks: false,
                        },
                    });
                },
            },
            this.checkPodsAreReadyNodeTask(),
            this.enablePortForwardingTask(),
            {
                title: 'Show user messages',
                skip: () => this.oneShotState.isActive(),
                task: () => {
                    this.logger.showAllMessageGroups();
                },
            },
        ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'mirror node add');
        if (tasks.isRoot()) {
            try {
                await tasks.run();
                this.logger.debug('mirror node add has completed');
            }
            catch (error) {
                throw new SoloError(`Error adding mirror node: ${error.message}`, error);
            }
            finally {
                if (!this.oneShotState.isActive()) {
                    await lease?.release();
                }
                await this.accountManager.close();
            }
        }
        else {
            this.taskList.registerCloseFunction(async () => {
                if (!this.oneShotState.isActive()) {
                    await lease?.release();
                }
                await this.accountManager.close();
            });
        }
        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);
                    if (!this.oneShotState.isActive()) {
                        lease = await this.leaseManager.create();
                    }
                    this.configManager.update(argv);
                    flags.disablePrompts(MirrorNodeCommand_1.UPGRADE_FLAGS_LIST.optional);
                    const allFlags = [
                        ...MirrorNodeCommand_1.UPGRADE_FLAGS_LIST.required,
                        ...MirrorNodeCommand_1.UPGRADE_FLAGS_LIST.optional,
                    ];
                    await this.configManager.executePrompt(task, allFlags);
                    const config = this.configManager.getConfig(MirrorNodeCommand_1.UPGRADE_CONFIGS_NAME, allFlags, []);
                    context_.config = config;
                    const hasMirrorNodeMemoryImprovements = new SemanticVersion(config.mirrorNodeVersion).greaterThanOrEqual(versions.MEMORY_ENHANCEMENTS_MIRROR_NODE_VERSION);
                    config.namespace = await this.getNamespace(task);
                    config.clusterReference = this.getClusterReference();
                    config.clusterContext = this.getClusterContext(config.clusterReference);
                    const { id, releaseName, isChartInstalled, ingressReleaseName, isLegacyChartInstalled } = await this.inferDestroyData(config.namespace, config.clusterContext);
                    config.id = id;
                    config.releaseName = releaseName;
                    config.isChartInstalled = isChartInstalled;
                    config.ingressReleaseName = ingressReleaseName;
                    config.isLegacyChartInstalled = isLegacyChartInstalled;
                    config.installSharedResources = false;
                    const currentMirrorNodeVersion = this.remoteConfig.getComponentVersion(ComponentTypes.MirrorNode);
                    if (currentMirrorNodeVersion && !currentMirrorNodeVersion.equals('0.0.0')) {
                        const targetMirrorNodeVersion = new SemanticVersion(config.mirrorNodeVersion);
                        if (targetMirrorNodeVersion.lessThanOrEqual(currentMirrorNodeVersion)) {
                            throw new SoloError(`Mirror node upgrade target version ${config.mirrorNodeVersion} is not newer than the current version ${currentMirrorNodeVersion.toString()} stored in remote config. ` +
                                'Use --mirror-node-version to specify a version newer than the currently deployed version.');
                        }
                    }
                    context_.config.soloChartVersion = SemanticVersion.getValidSemanticVersion(context_.config.soloChartVersion, false, 'Solo chart version');
                    const useMirrorNodeLegacyReleaseName = process.env.USE_MIRROR_NODE_LEGACY_RELEASE_NAME === 'true';
                    if (useMirrorNodeLegacyReleaseName) {
                        config.releaseName = constants.MIRROR_NODE_RELEASE_NAME;
                        config.ingressReleaseName = constants.INGRESS_CONTROLLER_RELEASE_NAME;
                    }
                    // predefined values first
                    config.valuesArg = new SemanticVersion(config.mirrorNodeVersion).lessThan(versions.POST_HIERO_MIGRATION_MIRROR_NODE_VERSION)
                        ? helpers.prepareValuesFiles(constants.MIRROR_NODE_VALUES_FILE_HEDERA)
                        : helpers.prepareValuesFiles(constants.MIRROR_NODE_VALUES_FILE);
                    // user defined values later to override predefined values
                    config.valuesArg += await this.prepareValuesArg(config);
                    const deploymentName = this.configManager.getFlag(flags.deployment);
                    await this.accountManager.loadNodeClient(config.namespace, this.remoteConfig.getClusterRefs(), deploymentName, this.configManager.getFlag(flags.forcePortForward));
                    const realm = this.localConfig.configuration.realmForDeployment(deploymentName);
                    const shard = this.localConfig.configuration.shardForDeployment(deploymentName);
                    const chartNamespace = this.getChartNamespace(config.mirrorNodeVersion);
                    const modules = ['monitor', 'rest', 'grpc', 'importer', 'restjava', 'graphql', 'rosetta', 'web3'];
                    for (const module of modules) {
                        config.valuesArg += ` --set ${module}.config.${chartNamespace}.mirror.common.realm=${realm}`;
                        config.valuesArg += ` --set ${module}.config.${chartNamespace}.mirror.common.shard=${shard}`;
                    }
                    if (config.pinger) {
                        if (!hasMirrorNodeMemoryImprovements) {
                            config.valuesArg += ' --set pinger.enabled=false';
                            config.valuesArg += ' --set monitor.enabled=true';
                            config.valuesArg += ` --set monitor.config.${chartNamespace}.mirror.monitor.publish.scenarios.pinger.tps=5`;
                        }
                        const operatorId = config.operatorId || this.accountManager.getOperatorAccountId(deploymentName).toString();
                        config.valuesArg += ` --set monitor.config.${chartNamespace}.mirror.monitor.operator.accountId=${operatorId}`;
                        config.valuesArg += ` --set pinger.env.HIERO_MIRROR_PINGER_OPERATOR_ID=${operatorId}`;
                        if (config.operatorKey) {
                            this.logger.info('Using provided operator key');
                            config.valuesArg += ` --set monitor.config.${chartNamespace}.mirror.monitor.operator.privateKey=${config.operatorKey}`;
                            config.valuesArg += ` --set pinger.env.HIERO_MIRROR_PINGER_OPERATOR_KEY=${config.operatorKey}`;
                        }
                        else {
                            try {
                                const namespace = await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task);
                                const secrets = await this.k8Factory
                                    .getK8(config.clusterContext)
                                    .secrets()
                                    .list(namespace, [`solo.hedera.com/account-id=${operatorId}`]);
                                if (secrets.length === 0) {
                                    this.logger.info(`No k8s secret found for operator account id ${operatorId}, use default one`);
                                    config.valuesArg += ` --set monitor.config.${chartNamespace}.mirror.monitor.operator.privateKey=${constants.OPERATOR_KEY}`;
                                    config.valuesArg += ` --set pinger.env.HIERO_MIRROR_PINGER_OPERATOR_KEY=${constants.OPERATOR_KEY}`;
                                }
                                else {
                                    this.logger.info('Using operator key from k8s secret');
                                    const operatorKeyFromK8 = Base64.decode(secrets[0].data.privateKey);
                                    config.valuesArg += ` --set monitor.config.${chartNamespace}.mirror.monitor.operator.privateKey=${operatorKeyFromK8}`;
                                    config.valuesArg += ` --set pinger.env.HIERO_MIRROR_PINGER_OPERATOR_KEY=${operatorKeyFromK8}`;
                                }
                            }
                            catch (error) {
                                throw new SoloError(`Error getting operator key: ${error.message}`, error);
                            }
                        }
                    }
                    else {
                        context_.config.valuesArg += ' --set monitor.enabled=false';
                        context_.config.valuesArg += ' --set pinger.enabled=false';
                    }
                    const isQuiet = config.quiet;
                    // In case the useExternalDatabase is set, prompt for the rest of the required data
                    if (config.useExternalDatabase && !isQuiet) {
                        await this.configManager.executePrompt(task, [
                            flags.externalDatabaseHost,
                            flags.externalDatabaseOwnerUsername,
                            flags.externalDatabaseOwnerPassword,
                            flags.externalDatabaseReadonlyUsername,
                            flags.externalDatabaseReadonlyPassword,
                        ]);
                    }
                    else if (config.useExternalDatabase &&
                        (!config.externalDatabaseHost ||
                            !config.externalDatabaseOwnerUsername ||
                            !config.externalDatabaseOwnerPassword ||
                            !config.externalDatabaseReadonlyUsername ||
                            !config.externalDatabaseReadonlyPassword)) {
                        this.validateExternalDatabaseFlags(config);
                    }
                    await this.throwIfNamespaceIsMissing(config.clusterContext, config.namespace);
                    this.addMirrorNodeMemoryOverrides(hasMirrorNodeMemoryImprovements, config);
                    const lockTask = this.oneShotState.isActive()
                        ? ListrLock.newSkippedLockTask(task)
                        : ListrLock.newAcquireLockTask(lease, task);
                    return lockTask;
                },
            },
            this.enableSharedResourcesTask(),
            this.deleteStaleRedisSecretTask(),
            this.primePostgresSecretTask(), // creates mirror-passwords secret if missing (e.g. re-install via upgrade)
            this.initializeSharedPostgresDatabaseTask(), // must run before mirror chart so importer doesn't hold a session during DB creation
            this.enableMirrorNodeTask(MirrorNodeCommandType.UPGRADE),
            this.checkPodsAreReadyNodeTask(),
            this.enablePortForwardingTask(),
            // TODO only show this if we are not running in quick-start mode
            // {
            //   title: 'Show user messages',
            //   task: (): void => {
            //     this.logger.showAllMessageGroups();
            //   },
            // },
        ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'mirror node upgrade');
        if (tasks.isRoot()) {
            try {
                await tasks.run();
                this.logger.debug('mirror node upgrade has completed');
            }
            catch (error) {
                throw new SoloError(`Error upgrading mirror node: ${error.message}`, error);
            }
            finally {
                if (!this.oneShotState.isActive()) {
                    await lease.release();
                }
                await this.accountManager.close();
            }
        }
        else {
            this.taskList.registerCloseFunction(async () => {
                if (!this.oneShotState.isActive()) {
                    await lease.release();
                }
                await this.accountManager.close();
            });
        }
        return true;
    }
    // Override values for mirror node memory optimizations
    addMirrorNodeMemoryOverrides(hasMirrorNodeMemoryImprovements, config) {
        const improvedMemoryModules = ['grpc', 'importer', 'rest', 'rest-java', 'web3'];
        if (!hasMirrorNodeMemoryImprovements) {
            for (const module of improvedMemoryModules) {
                const configRoot = module.replaceAll('-', '');
                config.valuesArg += ` --set ${configRoot}.image.registry=${constants.MIRROR_NODE_OLD_IMAGE_REGISTRY}`;
                config.valuesArg += ` --set ${configRoot}.image.repository=${constants.MIRROR_NODE_OLD_IMAGE_REPO_ROOT}${module}`;
                const memoryKey = `MIRROR_NODE_OLD_MEMORY_${configRoot.toUpperCase()}`;
                config.valuesArg += ` --set ${configRoot}.resources.limits.memory=${constants[memoryKey]}`;
            }
        }
        else if (process.arch === 'arm64') {
            /** Unable to build linux/arm64 native images due to limitation in web3j.
             * Upstream ticket https://github.com/LFDT-web3j/web3j-sokt/issues/40
             * will need to be resolved before we can disable this logic
             */
            config.valuesArg += ` --set web3.image.registry=${constants.MIRROR_NODE_OLD_IMAGE_REGISTRY}`;
            config.valuesArg += ` --set web3.image.repository=${constants.MIRROR_NODE_OLD_IMAGE_REPO_ROOT}web3`;
            config.valuesArg += ` --set web3.resources.limits.memory=${constants.MIRROR_NODE_OLD_MEMORY_WEB3}`;
        }
    }
    validateExternalDatabaseFlags(config) {
        const missingFlags = [];
        if (!config.externalDatabaseHost) {
            missingFlags.push(flags.externalDatabaseHost);
        }
        if (!config.externalDatabaseOwnerUsername) {
            missingFlags.push(flags.externalDatabaseOwnerUsername);
        }
        if (!config.externalDatabaseOwnerPassword) {
            missingFlags.push(flags.externalDatabaseOwnerPassword);
        }
        if (!config.externalDatabaseReadonlyUsername) {
            missingFlags.push(flags.externalDatabaseReadonlyUsername);
        }
        if (!config.externalDatabaseReadonlyPassword) {
            missingFlags.push(flags.externalDatabaseReadonlyPassword);
        }
        if (missingFlags.length > 0) {
            const errorMessage = 'There are missing values that need to be provided when' +
                `${chalk.cyan(`--${flags.useExternalDatabase.name}`)} is provided: `;
            throw new SoloError(`${errorMessage} ${missingFlags.map((flag) => `--${flag.name}`).join(', ')}`);
        }
    }
    getEnvironmentVariablePrefix(version) {
        return new SemanticVersion(version).lessThan(versions.POST_HIERO_MIGRATION_MIRROR_NODE_VERSION)
            ? 'HEDERA'
            : 'HIERO';
    }
    getChartNamespace(version) {
        return new SemanticVersion(version).lessThan(versions.POST_HIERO_MIGRATION_MIRROR_NODE_VERSION)
            ? 'hedera'
            : 'hiero';
    }
    /**
     * Encodes a shard.realm.num entity ID into the integer form used by the mirror node database.
     * Matches the encoding in EntityId.java: |10-bit shard|16-bit realm|38-bit num|
     */
    static encodeEntityId(shard, realm, entityNumber) {
        if (shard === 0 && realm === 0) {
            return String(entityNumber);
        }
        const NUM_BITS = 38n;
        const REALM_BITS = 16n;
        const encoded = (BigInt(entityNumber) & ((1n << NUM_BITS) - 1n)) |
            ((BigInt(realm) & ((1n << REALM_BITS) - 1n)) << NUM_BITS) |
            (BigInt(shard) << (REALM_BITS + NUM_BITS));
        return encoded.toString();
    }
    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);
                    if (!this.oneShotState.isActive()) {
                        lease = await this.leaseManager.create();
                    }
                    if (!argv.force) {
                        const confirmResult = await task.prompt(ListrInquirerPromptAdapter).run(confirmPrompt, {
                            default: false,
                            message: 'Are you sure you would like to destroy the mirror node components?',
                        });
                        if (!confirmResult) {
                            throw new UserBreak('Aborted application by user prompt');
                        }
                    }
                    this.configManager.update(argv);
                    const namespace = await this.getNamespace(task);
                    const clusterReference = this.getClusterReference();
                    const clusterContext = this.getClusterContext(clusterReference);
                    await this.throwIfNamespaceIsMissing(clusterContext, namespace);
                    const { id, releaseName, isChartInstalled, ingressReleaseName, isLegacyChartInstalled } = await this.inferDestroyData(namespace, clusterContext);
                    context_.config = {
                        clusterContext,
                        namespace,
                        clusterReference,
                        id,
                        isChartInstalled,
                        releaseName,
                        ingressReleaseName,
                        isLegacyChartInstalled,
                        isIngressControllerChartInstalled: await this.chartManager.isChartInstalled(namespace, ingressReleaseName, clusterContext),
                    };
                    if (!this.oneShotState.isActive()) {
                        return ListrLock.newAcquireLockTask(lease, task);
                    }
                    return ListrLock.newSkippedLockTask(task);
                },
            },
            {
                title: 'Destroy mirror-node',
                task: async (context_) => {
                    await this.chartManager.uninstall(context_.config.namespace, context_.config.releaseName, context_.config.clusterContext);
                },
                skip: (context_) => !context_.config.isChartInstalled,
            },
            {
                title: 'Delete PVCs',
                task: async (context_) => {
                    // filtering postgres and redis PVCs using instance labels
                    // since they have different name or component labels
                    const pvcs = await this.k8Factory
                        .getK8(context_.config.clusterContext)
                        .pvcs()
                        .list(context_.config.namespace, [`app.kubernetes.io/instance=${context_.config.releaseName}`]);
                    if (pvcs) {
                        for (const pvc of pvcs) {
                            await this.k8Factory
                                .getK8(context_.config.clusterContext)
                                .pvcs()
                                .delete(PvcReference.of(context_.config.namespace, PvcName.of(pvc)));
                        }
                    }
                },
                skip: (context_) => !context_.config.isChartInstalled,
            },
            {
                title: 'Destroy shared resources',
                task: async (context_) => {
                    await this.sharedResourceManager.uninstallChart(context_.config.namespace, context_.config.clusterContext);
                    // Delete PVCs left behind by the shared resources chart (Postgres data volume)
                    const pvcs = await this.k8Factory
                        .getK8(context_.config.clusterContext)
                        .pvcs()
                        .list(context_.config.namespace, ['app.kubernetes.io/instance=solo-shared-resources']);
                    for (const pvc of pvcs) {
                        await this.k8Factory
                            .getK8(context_.config.clusterContext)
                            .pvcs()
                            .delete(PvcReference.of(context_.config.namespace, PvcName.of(pvc)));
                    }
                },
            },
            this.disableSharedResourceComponents(),
            {
                title: 'Uninstall mirror ingress controller',
                skip: (context_) => !context_.config.isIngressControllerChartInstalled,
                task: async (context_) => {
                    await this.k8Factory
                        .getK8(context_.config.clusterContext)
                        .ingressClasses()
                        .delete(constants.MIRROR_INGRESS_CLASS_NAME);
                    if (await this.k8Factory
                        .getK8(context_.config.clusterContext)
                        .configMaps()
                        .exists(context_.config.namespace, 'ingress-controller-leader-' + constants.MIRROR_INGRESS_CLASS_NAME)) {
                        await this.k8Factory
                            .getK8(context_.config.clusterContext)
                            .configMaps()
                            .delete(context_.config.namespace, 'ingress-controller-leader-' + constants.MIRROR_INGRESS_CLASS_NAME);
                    }
                    await this.chartManager.uninstall(context_.config.namespace, context_.config.ingressReleaseName, context_.config.clusterContext);
                    // delete ingress class if found one
                    const existingIngressClasses = await this.k8Factory
                        .getK8(context_.config.clusterContext)
                        .ingressClasses()
                        .list();
                    existingIngressClasses.map((ingressClass) => {
                        if (ingressClass.name === constants.MIRROR_INGRESS_CLASS_NAME) {
                            this.k8Factory
                                .getK8(context_.config.clusterContext)
                                .ingressClasses()
                                .delete(constants.MIRROR_INGRESS_CLASS_NAME);
                        }
                    });
                },
            },
            this.disableMirrorNodeComponents(),
        ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT, undefined, 'mirror node destroy');
        if (tasks.isRoot()) {
            try {
                await tasks.run();
            }
            catch (error) {
                throw new SoloError(`Error destroying mirror node: ${error.message}`, error);
            }
            finally {
                await this.accountManager?.close().catch();
                if (!this.oneShotState.isActive()) {
                    await lease?.release();
                }
            }
        }
        else {
            this.taskList.registerCloseFunction(async () => {
                await this.accountManager?.close().catch();
                if (!this.oneShotState.isActive()) {
                    await lease?.release();
                }
            });
        }
        return true;
    }
    /** Removes the mirror node components from remote config. */
    disableMirrorNodeComponents() {
        return {
            title: 'Remove mirror node from remote config',
            skip: () => !this.remoteConfig.isLoaded(),
            task: async (context_) => {
                this.remoteConfig.configuration.components.removeComponent(context_.config.id, ComponentTypes.MirrorNode);
                await this.remoteConfig.persist();
            },
        };
    }
    /** Removes the Postgres and Redis components from remote config when shared resources are destroyed. */
    disableSharedResourceComponents() {
        return {
            title: 'Remove shared resource components from remote config',
            skip: () => !this.remoteConfig.isLoaded(),
            task: async () => {
                const postgresComponents = this.remoteConfig.configuration.components.getComponentByType(ComponentTypes.Postgres);
                for (const component of postgresComponents) {
                    this.remoteConfig.configuration.components.removeComponent(component.metadata.id, ComponentTypes.Postgres);
                }
                const redisComponents = this.remoteConfig.configuration.components.getComponentByType(ComponentTypes.Redis);
                for (const component of redisComponents) {
                    this.remoteConfig.configuration.components.removeComponent(component.metadata.id, ComponentTypes.Redis);
                }
                await this.remoteConfig.persist();
            },
        };
    }
    /** Adds the mirror node components to remote config. */
    addMirrorNodeComponents() {
        return {
            title: 'Add mirror node to remote config',
            skip: (context_) => {
                return !this.remoteConfig.isLoaded() || context_.config.isChartInstalled || this.oneShotState.isActive();
            },
            task: async (context_) => {
                this.remoteConfig.configuration.components.addNewComponent(context_.config.newMirrorNodeComponent, ComponentTypes.MirrorNode);
                // update mirror node version in remote config
                this.remoteConfig.updateComponentVersion(ComponentTypes.MirrorNode, new SemanticVersion(context_.config.mirrorNodeVersion));
                await this.remoteConfig.persist();
            },
        };
    }
    async close() { } // no-op
    async checkIfLegacyChartIsInstalled(id, namespace, context) {
        return id === 1
            ? await this.chartManager.isChartInstalled(namespace, constants.MIRROR_NODE_RELEASE_NAME, context)
            : false;
    }
    inferMirrorNodeId() {
        const id = this.configManager.getFlag(flags.id);
        if (typeof id === 'number') {
            return id;
        }
        if (this.remoteConfig.configuration.components.state.mirrorNodes.length === 0) {
            throw new SoloError('Mirror node not found in remote config');
        }
        return this.remoteConfig.configuration.components.state.mirrorNodes[0].metadata.id;
    }
    async inferDestroyData(namespace, context) {
        const id = this.inferMirrorNodeId();
        const isLegacyChartInstalled = await this.checkIfLegacyChartIsInstalled(id, namespace, context);
        const ingressReleaseName = await this.inferInstalledIngressReleaseName(namespace, context, id);
        if (isLegacyChartInstalled) {
            return {
                id,
                releaseName: constants.MIRROR_NODE_RELEASE_NAME,
                isChartInstalled: true,
                ingressReleaseName,
                isLegacyChartInstalled,
            };
        }
        const releaseName = this.renderReleaseName(id);
        return {
            id,
            releaseName,
            isChartInstalled: await this.chartManager.isChartInstalled(namespace, releaseName, context),
            ingressReleaseName,
            isLegacyChartInstalled,
        };
    }
    async inferInstalledIngressReleaseName(namespace, context, id) {
        const candidates = [
            this.renderIngressReleaseName(id),
            `${constants.INGRESS_CONTROLLER_RELEASE_NAME}-${namespace.name}`,
            constants.INGRESS_CONTROLLER_RELEASE_NAME,
        ];
        for (const releaseName of candidates) {
            if (await this.chartManager.isChartInstalled(namespace, releaseName, context)) {
                return releaseName;
            }
        }
        // Keep existing behavior as fallback when no ingress release is currently installed.
        return this.renderIngressReleaseName(id);
    }
    async adoptMirrorIngressControllerRbacOwnership(config) {
        const rbac = this.k8Factory.getK8(config.clusterContext).rbac();
        const rbacNames = new Set([
            constants.MIRROR_INGRESS_CONTROLLER,
            `${constants.MIRROR_INGRESS_CONTROLLER}-${config.namespace.name}`,
        ]);
        for (const rbacName of rbacNames) {
            await rbac.setHelmOwnership(rbacName, config.ingressReleaseName, config.namespace.name);
        }
    }
};
MirrorNodeCommand = MirrorNodeCommand_1 = __decorate([
    injectable(),
    __param(0, inject(InjectTokens.PostgresSharedResource)),
    __param(1, inject(InjectTokens.SharedResourceManager)),
    __param(2, inject(InjectTokens.AccountManager)),
    __param(3, inject(InjectTokens.SoloEventBus)),
    __metadata("design:paramtypes", [PostgresSharedResource,
        SharedResourceManager, Function, Object])
], MirrorNodeCommand);
export { MirrorNodeCommand };
//# sourceMappingURL=mirror-node.js.map