/**
 * Copyright (C) 2024 Hedera Hashgraph, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the ""License"");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an ""AS IS"" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
import { Listr } from 'listr2';
import { SoloError, MissingArgumentError } from '../core/errors.js';
import * as helpers from '../core/helpers.js';
import * as constants from '../core/constants.js';
import { BaseCommand } from './base.js';
import { Flags as flags } from './flags.js';
import { getNodeAccountMap } from '../core/helpers.js';
import { RemoteConfigTasks } from '../core/config/remote/remote_config_tasks.js';
import { ListrLease } from '../core/lease/listr_lease.js';
import { RelayComponent } from '../core/config/remote/components/relay_component.js';
import { ComponentType } from '../core/config/remote/enumerations.js';
export class RelayCommand extends BaseCommand {
    profileManager;
    accountManager;
    constructor(opts) {
        super(opts);
        if (!opts || !opts.profileManager)
            throw new MissingArgumentError('An instance of core/ProfileManager is required', opts.downloader);
        this.profileManager = opts.profileManager;
        this.accountManager = opts.accountManager;
    }
    static get DEPLOY_CONFIGS_NAME() {
        return 'deployConfigs';
    }
    static get DEPLOY_FLAGS_LIST() {
        return [
            flags.chainId,
            flags.chartDirectory,
            flags.namespace,
            flags.nodeAliasesUnparsed,
            flags.operatorId,
            flags.operatorKey,
            flags.profileFile,
            flags.profileName,
            flags.quiet,
            flags.relayReleaseTag,
            flags.replicaCount,
            flags.valuesFile,
        ];
    }
    static get DESTROY_FLAGS_LIST() {
        return [flags.chartDirectory, flags.namespace, flags.nodeAliasesUnparsed];
    }
    async prepareValuesArg(valuesFile, nodeAliases, chainID, relayRelease, replicaCount, operatorID, operatorKey, namespace) {
        let valuesArg = '';
        const profileName = this.configManager.getFlag(flags.profileName);
        const profileValuesFile = await this.profileManager.prepareValuesForRpcRelayChart(profileName);
        if (profileValuesFile) {
            valuesArg += this.prepareValuesFiles(profileValuesFile);
        }
        valuesArg += ` --set config.MIRROR_NODE_URL=http://${constants.MIRROR_NODE_RELEASE_NAME}-rest`;
        valuesArg += ` --set config.MIRROR_NODE_URL_WEB3=http://${constants.MIRROR_NODE_RELEASE_NAME}-web3`;
        valuesArg += ' --set config.MIRROR_NODE_AGENT_CACHEABLE_DNS=false';
        valuesArg += ' --set config.MIRROR_NODE_RETRY_DELAY=2001';
        valuesArg += ' --set config.MIRROR_NODE_GET_CONTRACT_RESULTS_DEFAULT_RETRIES=21';
        if (chainID) {
            valuesArg += ` --set config.CHAIN_ID=${chainID}`;
        }
        if (relayRelease) {
            valuesArg += ` --set image.tag=${relayRelease.replace(/^v/, '')}`;
        }
        if (replicaCount) {
            valuesArg += ` --set replicaCount=${replicaCount}`;
        }
        if (operatorID) {
            valuesArg += ` --set config.OPERATOR_ID_MAIN=${operatorID}`;
        }
        if (operatorKey) {
            valuesArg += ` --set config.OPERATOR_KEY_MAIN=${operatorKey}`;
        }
        if (!nodeAliases) {
            throw new MissingArgumentError('Node IDs must be specified');
        }
        const networkJsonString = await this.prepareNetworkJsonString(nodeAliases, namespace);
        valuesArg += ` --set config.HEDERA_NETWORK='${networkJsonString}'`;
        if (valuesFile) {
            valuesArg += this.prepareValuesFiles(valuesFile);
        }
        return valuesArg;
    }
    /**
     * created a json string to represent the map between the node keys and their ids
     * output example '{"node-1": "0.0.3", "node-2": "0.004"}'
     */
    async prepareNetworkJsonString(nodeAliases = [], namespace) {
        if (!nodeAliases) {
            throw new MissingArgumentError('Node IDs must be specified');
        }
        const networkIds = {};
        const accountMap = getNodeAccountMap(nodeAliases);
        const networkNodeServicesMap = await this.accountManager.getNodeServiceMap(namespace);
        nodeAliases.forEach(nodeAlias => {
            const haProxyClusterIp = networkNodeServicesMap.get(nodeAlias).haProxyClusterIp;
            const haProxyGrpcPort = networkNodeServicesMap.get(nodeAlias).haProxyGrpcPort;
            const networkKey = `${haProxyClusterIp}:${haProxyGrpcPort}`;
            networkIds[networkKey] = accountMap.get(nodeAlias);
        });
        return JSON.stringify(networkIds);
    }
    prepareReleaseName(nodeAliases = []) {
        if (!nodeAliases) {
            throw new MissingArgumentError('Node IDs must be specified');
        }
        let releaseName = 'relay';
        nodeAliases.forEach(nodeAlias => {
            releaseName += `-${nodeAlias}`;
        });
        return releaseName;
    }
    async deploy(argv) {
        const self = this;
        const lease = await self.leaseManager.create();
        const tasks = new Listr([
            {
                title: 'Initialize',
                task: async (ctx, task) => {
                    // reset nodeAlias
                    self.configManager.setFlag(flags.nodeAliasesUnparsed, '');
                    self.configManager.update(argv);
                    await self.configManager.executePrompt(task, RelayCommand.DEPLOY_FLAGS_LIST);
                    // prompt if inputs are empty and set it in the context
                    ctx.config = this.getConfig(RelayCommand.DEPLOY_CONFIGS_NAME, RelayCommand.DEPLOY_FLAGS_LIST, [
                        'nodeAliases',
                    ]);
                    ctx.config.nodeAliases = helpers.parseNodeAliases(ctx.config.nodeAliasesUnparsed);
                    ctx.config.releaseName = self.prepareReleaseName(ctx.config.nodeAliases);
                    ctx.config.isChartInstalled = await self.chartManager.isChartInstalled(ctx.config.namespace, ctx.config.releaseName);
                    self.logger.debug('Initialized config', { config: ctx.config });
                    return ListrLease.newAcquireLeaseTask(lease, task);
                },
            },
            RemoteConfigTasks.loadRemoteConfig.bind(this)(argv),
            {
                title: 'Prepare chart values',
                task: async (ctx) => {
                    const config = ctx.config;
                    config.chartPath = await self.prepareChartPath(config.chartDirectory, constants.JSON_RPC_RELAY_CHART, constants.JSON_RPC_RELAY_CHART);
                    config.valuesArg = await self.prepareValuesArg(config.valuesFile, config.nodeAliases, config.chainId, config.relayReleaseTag, config.replicaCount, config.operatorId, config.operatorKey, config.namespace);
                },
            },
            {
                title: 'Deploy JSON RPC Relay',
                task: async (ctx) => {
                    const config = ctx.config;
                    await self.chartManager.install(config.namespace, config.releaseName, config.chartPath, '', config.valuesArg);
                    await self.k8.waitForPods([constants.POD_PHASE_RUNNING], ['app=hedera-json-rpc-relay', `app.kubernetes.io/instance=${config.releaseName}`], 1, constants.RELAY_PODS_RUNNING_MAX_ATTEMPTS, constants.RELAY_PODS_RUNNING_DELAY);
                    // reset nodeAlias
                    self.configManager.setFlag(flags.nodeAliasesUnparsed, '');
                },
            },
            {
                title: 'Check relay is ready',
                task: async (ctx) => {
                    const config = ctx.config;
                    try {
                        await self.k8.waitForPodReady(['app=hedera-json-rpc-relay', `app.kubernetes.io/instance=${config.releaseName}`], 1, constants.RELAY_PODS_READY_MAX_ATTEMPTS, constants.RELAY_PODS_READY_DELAY);
                    }
                    catch (e) {
                        throw new SoloError(`Relay ${config.releaseName} is not ready: ${e.message}`, e);
                    }
                },
            },
            this.addRelayComponent(),
        ], {
            concurrent: false,
            rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION,
        });
        try {
            await tasks.run();
        }
        catch (e) {
            throw new SoloError('Error installing relays', e);
        }
        finally {
            await lease.release();
        }
        return true;
    }
    async destroy(argv) {
        const self = this;
        const lease = await self.leaseManager.create();
        const tasks = new Listr([
            {
                title: 'Initialize',
                task: async (ctx, task) => {
                    // reset nodeAlias
                    self.configManager.setFlag(flags.nodeAliasesUnparsed, '');
                    self.configManager.update(argv);
                    await self.configManager.executePrompt(task, RelayCommand.DESTROY_FLAGS_LIST);
                    // prompt if inputs are empty and set it in the context
                    ctx.config = {
                        chartDirectory: self.configManager.getFlag(flags.chartDirectory),
                        namespace: self.configManager.getFlag(flags.namespace),
                        nodeAliases: helpers.parseNodeAliases(self.configManager.getFlag(flags.nodeAliasesUnparsed)),
                    };
                    ctx.config.releaseName = this.prepareReleaseName(ctx.config.nodeAliases);
                    ctx.config.isChartInstalled = await this.chartManager.isChartInstalled(ctx.config.namespace, ctx.config.releaseName);
                    self.logger.debug('Initialized config', { config: ctx.config });
                    return ListrLease.newAcquireLeaseTask(lease, task);
                },
            },
            RemoteConfigTasks.loadRemoteConfig.bind(this)(argv),
            {
                title: 'Destroy JSON RPC Relay',
                task: async (ctx) => {
                    const config = ctx.config;
                    await this.chartManager.uninstall(config.namespace, config.releaseName);
                    this.logger.showList('Destroyed Relays', await self.chartManager.getInstalledCharts(config.namespace));
                    // reset nodeAliasesUnparsed
                    self.configManager.setFlag(flags.nodeAliasesUnparsed, '');
                },
                skip: ctx => !ctx.config.isChartInstalled,
            },
            this.removeRelayComponent(),
        ], {
            concurrent: false,
            rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION,
        });
        try {
            await tasks.run();
        }
        catch (e) {
            throw new SoloError('Error uninstalling relays', e);
        }
        finally {
            await lease.release();
        }
        return true;
    }
    getCommandDefinition() {
        const self = this;
        return {
            command: 'relay',
            desc: 'Manage JSON RPC relays in solo network',
            builder: (yargs) => {
                return yargs
                    .command({
                    command: 'deploy',
                    desc: 'Deploy a JSON RPC relay',
                    builder: (y) => {
                        flags.setCommandFlags(y, ...RelayCommand.DEPLOY_FLAGS_LIST);
                    },
                    handler: (argv) => {
                        self.logger.info("==== Running 'relay deploy' ===", { argv });
                        self.logger.info(argv);
                        self
                            .deploy(argv)
                            .then(r => {
                            self.logger.info('==== Finished running `relay deploy`====');
                            if (!r)
                                process.exit(1);
                        })
                            .catch(err => {
                            self.logger.showUserError(err);
                            process.exit(1);
                        });
                    },
                })
                    .command({
                    command: 'destroy',
                    desc: 'Destroy JSON RPC relay',
                    builder: (y) => flags.setCommandFlags(y, flags.chartDirectory, flags.namespace, flags.nodeAliasesUnparsed),
                    handler: (argv) => {
                        self.logger.info("==== Running 'relay destroy' ===", { argv });
                        self.logger.debug(argv);
                        self.destroy(argv).then(r => {
                            self.logger.info('==== Finished running `relay destroy`====');
                            if (!r)
                                process.exit(1);
                        });
                    },
                })
                    .demandCommand(1, 'Select a relay command');
            },
        };
    }
    /** Adds the relay component to remote config. */
    addRelayComponent() {
        return {
            title: 'Add relay component in remote config',
            skip: () => !this.remoteConfigManager.isLoaded(),
            task: async (ctx) => {
                await this.remoteConfigManager.modify(async (remoteConfig) => {
                    const { config: { namespace, nodeAliases }, } = ctx;
                    const cluster = this.remoteConfigManager.currentCluster;
                    remoteConfig.components.add('relay', new RelayComponent('relay', cluster, namespace, nodeAliases));
                });
            },
        };
    }
    /** Remove the relay component from remote config. */
    removeRelayComponent() {
        return {
            title: 'Remove relay component from remote config',
            skip: () => !this.remoteConfigManager.isLoaded(),
            task: async () => {
                await this.remoteConfigManager.modify(async (remoteConfig) => {
                    remoteConfig.components.remove('relay', ComponentType.Relay);
                });
            },
        };
    }
    close() {
        // no-op
        return Promise.resolve();
    }
}
//# sourceMappingURL=relay.js.map