"use strict";
/*
 * Copyright 2022 Fernando Boucquez
 *
 * 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.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigService = exports.KeyName = exports.defaultAssembly = exports.Assembly = exports.Preset = void 0;
const fs = require("fs");
const fs_1 = require("fs");
const _ = require("lodash");
const path_1 = require("path");
const symbol_sdk_1 = require("symbol-sdk");
const BootstrapUtils_1 = require("./BootstrapUtils");
const CertificateService_1 = require("./CertificateService");
const CommandUtils_1 = require("./CommandUtils");
const ConfigLoader_1 = require("./ConfigLoader");
const CryptoUtils_1 = require("./CryptoUtils");
const NemgenService_1 = require("./NemgenService");
const RemoteNodeService_1 = require("./RemoteNodeService");
const ReportService_1 = require("./ReportService");
const VotingService_1 = require("./VotingService");
/**
 * Defined presets.
 */
var Preset;
(function (Preset) {
    Preset["bootstrap"] = "bootstrap";
    Preset["testnet"] = "testnet";
    Preset["mainnet"] = "mainnet";
})(Preset = exports.Preset || (exports.Preset = {}));
var Assembly;
(function (Assembly) {
    Assembly["api"] = "api";
    Assembly["demo"] = "demo";
    Assembly["dual"] = "dual";
    Assembly["multinode"] = "multinode";
    Assembly["peer"] = "peer";
})(Assembly = exports.Assembly || (exports.Assembly = {}));
exports.defaultAssembly = {
    [Preset.bootstrap]: Assembly.multinode,
};
var KeyName;
(function (KeyName) {
    KeyName["Main"] = "Main";
    KeyName["Remote"] = "Remote";
    KeyName["Transport"] = "Transport";
    KeyName["Voting"] = "Voting";
    KeyName["VRF"] = "VRF";
    KeyName["NemesisSigner"] = "Nemesis Signer";
    KeyName["NemesisAccount"] = "Nemesis Account";
    KeyName["ServiceProvider"] = "Service Provider";
})(KeyName = exports.KeyName || (exports.KeyName = {}));
class ConfigService {
    constructor(logger, params) {
        this.logger = logger;
        this.params = params;
        this.configLoader = new ConfigLoader_1.ConfigLoader(logger);
    }
    resolveConfigPreset(password) {
        const target = this.params.target;
        const presetLocation = this.configLoader.getGeneratedPresetLocation(target);
        if (fs.existsSync(presetLocation) && !this.params.upgrade) {
            return this.configLoader.loadExistingPresetData(target, password);
        }
        const oldPresetData = this.configLoader.loadExistingPresetDataIfPreset(target, password);
        return this.resolveCurrentPresetData(oldPresetData, password);
    }
    async run() {
        const target = this.params.target;
        try {
            if (this.params.reset) {
                BootstrapUtils_1.BootstrapUtils.deleteFolder(this.logger, target);
            }
            const presetLocation = this.configLoader.getGeneratedPresetLocation(target);
            const addressesLocation = this.configLoader.getGeneratedAddressLocation(target);
            const password = this.params.password;
            if (fs.existsSync(presetLocation) && !this.params.upgrade) {
                this.logger.info(`The generated preset ${presetLocation} already exist, ignoring configuration. (run -r to reset or --upgrade to upgrade)`);
                const presetData = this.configLoader.loadExistingPresetData(target, password);
                const addresses = this.configLoader.loadExistingAddresses(target, password);
                if (this.params.report) {
                    await new ReportService_1.ReportService(this.logger, this.params).run(presetData);
                }
                return { presetData, addresses };
            }
            const oldPresetData = this.configLoader.loadExistingPresetDataIfPreset(target, password);
            if (oldPresetData) {
                // HACK! https://github.com/symbol/symbol-bootstrap/pull/270 would fix this!
                delete oldPresetData.knownPeers;
                delete oldPresetData.knownRestGateways;
            }
            const oldAddresses = this.configLoader.loadExistingAddressesIfPreset(target, password);
            if (oldAddresses && !oldPresetData) {
                throw new BootstrapUtils_1.KnownError(`Configuration cannot be upgraded without a previous ${presetLocation} file. (run -r to reset)`);
            }
            if (!oldAddresses && oldPresetData) {
                throw new BootstrapUtils_1.KnownError(`Configuration cannot be upgraded without a previous ${addressesLocation} file. (run -r to reset)`);
            }
            if (oldAddresses && oldPresetData) {
                this.logger.info('Upgrading configuration...');
            }
            const presetData = this.resolveCurrentPresetData(oldPresetData, password);
            const addresses = await this.configLoader.generateRandomConfiguration(oldAddresses, oldPresetData, presetData);
            const privateKeySecurityMode = CryptoUtils_1.CryptoUtils.getPrivateKeySecurityMode(presetData.privateKeySecurityMode);
            await BootstrapUtils_1.BootstrapUtils.mkdir(target);
            const remoteNodeService = new RemoteNodeService_1.RemoteNodeService(this.logger, presetData, this.params.offline);
            this.cleanUpConfiguration(presetData);
            await this.generateNodeCertificates(presetData, addresses);
            await this.generateNodes(presetData, addresses, remoteNodeService);
            await this.generateGateways(presetData);
            await this.generateExplorers(presetData, remoteNodeService);
            const isUpgrade = !!oldPresetData || !!oldAddresses;
            await this.resolveNemesis(presetData, addresses, isUpgrade);
            await this.copyNemesis(addresses);
            if (this.params.report) {
                await new ReportService_1.ReportService(this.logger, this.params).run(presetData);
            }
            await BootstrapUtils_1.BootstrapUtils.writeYaml(addressesLocation, CryptoUtils_1.CryptoUtils.removePrivateKeysAccordingToSecurityMode(addresses, privateKeySecurityMode), password);
            await BootstrapUtils_1.BootstrapUtils.writeYaml(presetLocation, CryptoUtils_1.CryptoUtils.removePrivateKeys(presetData), password);
            this.logger.info(`Configuration generated.`);
            return { presetData, addresses };
        }
        catch (e) {
            if (e.known) {
                this.logger.error(e.message);
            }
            else {
                this.logger.error(`Unknown error generating the configuration. ${e.message}`, e);
                this.logger.error(`The target folder '${target}' should be deleted!!!`);
            }
            throw e;
        }
    }
    resolveCurrentPresetData(oldPresetData, password) {
        return this.configLoader.createPresetData(Object.assign(Object.assign({}, this.params), { workingDir: this.params.workingDir, password: password, oldPresetData }));
    }
    async copyNemesis(addresses) {
        const target = this.params.target;
        const nemesisSeedFolder = BootstrapUtils_1.BootstrapUtils.getTargetNemesisFolder(target, false, 'seed');
        await this.validateSeedFolder(nemesisSeedFolder, `Invalid final seed folder ${nemesisSeedFolder}`);
        await Promise.all((addresses.nodes || []).map(async (account) => {
            const name = account.name;
            const dataFolder = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(target, false, name, 'data');
            await BootstrapUtils_1.BootstrapUtils.mkdir(dataFolder);
            const seedFolder = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(target, false, name, 'seed');
            await BootstrapUtils_1.BootstrapUtils.generateConfiguration({}, nemesisSeedFolder, seedFolder);
        }));
    }
    async validateSeedFolder(nemesisSeedFolder, message) {
        BootstrapUtils_1.BootstrapUtils.validateFolder(nemesisSeedFolder);
        const seedData = path_1.join(nemesisSeedFolder, '00000', '00001.dat');
        if (!fs_1.existsSync(seedData)) {
            throw new BootstrapUtils_1.KnownError(`File ${seedData} doesn't exist! ${message}`);
        }
        const seedIndex = path_1.join(nemesisSeedFolder, 'index.dat');
        if (!fs_1.existsSync(seedIndex)) {
            throw new BootstrapUtils_1.KnownError(`File ${seedIndex} doesn't exist! ${message}`);
        }
    }
    async resolveNemesis(presetData, addresses, isUpgrade) {
        const target = this.params.target;
        const nemesisSeedFolder = BootstrapUtils_1.BootstrapUtils.getTargetNemesisFolder(target, false, 'seed');
        await BootstrapUtils_1.BootstrapUtils.mkdir(nemesisSeedFolder);
        if (ConfigLoader_1.ConfigLoader.shouldCreateNemesis(presetData)) {
            if (isUpgrade) {
                this.logger.info('Nemesis data cannot be generated when upgrading...');
            }
            else {
                BootstrapUtils_1.BootstrapUtils.deleteFolder(this.logger, nemesisSeedFolder);
                await BootstrapUtils_1.BootstrapUtils.mkdir(nemesisSeedFolder);
                await this.generateNemesisConfig(presetData, addresses);
                await this.validateSeedFolder(nemesisSeedFolder, `Is the generated nemesis seed a valid seed folder?`);
            }
            return;
        }
        if (isUpgrade) {
            this.logger.info('Upgrading genesis on upgrade!');
        }
        const resolvePresetNemesisSeedFolder = () => {
            if (!presetData.nemesisSeedFolder) {
                return undefined;
            }
            return BootstrapUtils_1.BootstrapUtils.resolveWorkingDirPath(this.params.workingDir, presetData.nemesisSeedFolder);
        };
        const presetNemesisSeedFolder = resolvePresetNemesisSeedFolder();
        if (presetNemesisSeedFolder) {
            await this.validateSeedFolder(presetNemesisSeedFolder, `Is the provided preset nemesisSeedFolder: ${presetNemesisSeedFolder} a valid seed folder?`);
            this.logger.info(`Using custom nemesis seed folder in ${presetNemesisSeedFolder}`);
            BootstrapUtils_1.BootstrapUtils.deleteFolder(this.logger, nemesisSeedFolder);
            await BootstrapUtils_1.BootstrapUtils.mkdir(nemesisSeedFolder);
            await BootstrapUtils_1.BootstrapUtils.generateConfiguration({}, presetNemesisSeedFolder, nemesisSeedFolder);
            await this.validateSeedFolder(nemesisSeedFolder, `Is the ${presetData.preset} preset default seed a valid seed folder?`);
            return;
        }
        if (BootstrapUtils_1.BootstrapUtils.isYmlFile(presetData.preset)) {
            throw new BootstrapUtils_1.KnownError(`Seed for preset ${presetData.preset} could not be found. Please provide 'nemesisSeedFolder'!`);
        }
        else {
            const networkNemesisSeed = path_1.join(BootstrapUtils_1.BootstrapUtils.ROOT_FOLDER, 'presets', presetData.preset, 'seed');
            if (fs_1.existsSync(networkNemesisSeed)) {
                BootstrapUtils_1.BootstrapUtils.deleteFolder(this.logger, nemesisSeedFolder);
                await BootstrapUtils_1.BootstrapUtils.mkdir(nemesisSeedFolder);
                await BootstrapUtils_1.BootstrapUtils.generateConfiguration({}, networkNemesisSeed, nemesisSeedFolder);
                await this.validateSeedFolder(nemesisSeedFolder, `Is the ${presetData.preset} preset default seed a valid seed folder?`);
                return;
            }
            this.logger.warn(`Seed for preset ${presetData.preset} could not be found in ${networkNemesisSeed}`);
            throw new Error('Seed could not be found!!!!');
        }
    }
    async generateNodes(presetData, addresses, remoteNodeService) {
        const currentFinalizationEpoch = await remoteNodeService.resolveCurrentFinalizationEpoch();
        const externalPeers = await remoteNodeService.getPeerInfos();
        const localPeers = (presetData.nodes || []).map((nodePresetData, index) => {
            const node = (addresses.nodes || [])[index];
            return {
                publicKey: node.main.publicKey,
                endpoint: {
                    host: nodePresetData.host || '',
                    port: 7900,
                },
                metadata: {
                    name: nodePresetData.friendlyName || '',
                    roles: ConfigLoader_1.ConfigLoader.resolveRoles(nodePresetData),
                },
            };
        });
        const allPeers = _.uniqBy([...externalPeers, ...localPeers], (p) => p.publicKey);
        await Promise.all((addresses.nodes || []).map((account, index) => this.generateNodeConfiguration(account, index, presetData, currentFinalizationEpoch, allPeers)));
    }
    async generateNodeCertificates(presetData, addresses) {
        await Promise.all((addresses.nodes || []).map((account) => {
            const providedCertificates = {
                main: account.main,
                transport: account.transport,
            };
            return new CertificateService_1.CertificateService(this.logger, this.params).run(presetData, account.name, providedCertificates, false);
        }));
    }
    async generateNodeConfiguration(account, index, presetData, currentFinalizationEpoch, knownPeers) {
        const copyFrom = path_1.join(BootstrapUtils_1.BootstrapUtils.ROOT_FOLDER, 'config', 'node');
        const name = account.name;
        const serverConfig = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(this.params.target, false, name, 'server-config');
        const brokerConfig = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(this.params.target, false, name, 'broker-config');
        const dataFolder = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(this.params.target, false, name, 'data');
        await BootstrapUtils_1.BootstrapUtils.mkdir(dataFolder);
        const nodePreset = (presetData.nodes || [])[index];
        const harvesterSigningPrivateKey = nodePreset.harvesting
            ? await CommandUtils_1.CommandUtils.resolvePrivateKey(this.logger, presetData.networkType, account.remote || account.main, account.remote ? KeyName.Remote : KeyName.Main, account.name, 'storing the harvesterSigningPrivateKey in the server properties')
            : '';
        const harvesterVrfPrivateKey = await CommandUtils_1.CommandUtils.resolvePrivateKey(this.logger, presetData.networkType, account.vrf, KeyName.VRF, account.name, 'storing the harvesterVrfPrivateKey in the server properties');
        const beneficiaryAddress = nodePreset.beneficiaryAddress || presetData.beneficiaryAddress;
        const generatedContext = {
            name: name,
            friendlyName: (nodePreset === null || nodePreset === void 0 ? void 0 : nodePreset.friendlyName) || account.friendlyName,
            harvesterSigningPrivateKey: harvesterSigningPrivateKey,
            harvesterVrfPrivateKey: harvesterVrfPrivateKey,
            unfinalizedBlocksDuration: nodePreset.voting
                ? presetData.votingUnfinalizedBlocksDuration
                : presetData.nonVotingUnfinalizedBlocksDuration,
            beneficiaryAddress: beneficiaryAddress == undefined ? account.main.address : beneficiaryAddress,
            roles: ConfigLoader_1.ConfigLoader.resolveRoles(nodePreset),
        };
        const templateContext = Object.assign(Object.assign(Object.assign({}, presetData), generatedContext), nodePreset);
        const excludeFiles = [];
        // Exclude files depending on the enabled extensions. To complete...
        if (!templateContext.harvesting) {
            excludeFiles.push('config-harvesting.properties');
        }
        if (!templateContext.networkheight) {
            excludeFiles.push('config-networkheight.properties');
        }
        const serverRecoveryConfig = {
            addressextractionRecovery: false,
            mongoRecovery: false,
            zeromqRecovery: false,
            filespoolingRecovery: true,
            hashcacheRecovery: true,
        };
        const brokerRecoveryConfig = {
            addressextractionRecovery: true,
            mongoRecovery: true,
            zeromqRecovery: true,
            filespoolingRecovery: false,
            hashcacheRecovery: true,
        };
        this.logger.info(`Generating ${name} server configuration`);
        await BootstrapUtils_1.BootstrapUtils.generateConfiguration(Object.assign(Object.assign({}, serverRecoveryConfig), templateContext), copyFrom, serverConfig, excludeFiles);
        const isPeer = (nodePresetData) => nodePresetData.metadata.roles.includes('Peer');
        const peers = knownPeers.filter((peer) => isPeer(peer) && peer.publicKey != account.main.publicKey);
        const peersP2PFile = await this.generateP2PFile(peers, presetData.peersP2PListLimit, serverConfig, `this file contains a list of peers`, 'peers-p2p.json');
        const isApi = (nodePresetData) => nodePresetData.metadata.roles.includes('Api');
        const apiPeers = knownPeers.filter((peer) => isApi(peer) && peer.publicKey != account.main.publicKey);
        const peersApiFile = await this.generateP2PFile(apiPeers, presetData.peersApiListLimit, serverConfig, `this file contains a list of api peers`, 'peers-api.json');
        if (!peers.length && !apiPeers.length) {
            this.logger.warn('The peer lists could not be resolved. peers-p2p.json and peers-api.json are empty!');
        }
        if (nodePreset.brokerName) {
            this.logger.info(`Generating ${nodePreset.brokerName} broker configuration`);
            await BootstrapUtils_1.BootstrapUtils.generateConfiguration(Object.assign(Object.assign({}, brokerRecoveryConfig), templateContext), copyFrom, brokerConfig, excludeFiles);
            fs_1.copyFileSync(peersP2PFile, path_1.join(path_1.join(brokerConfig, 'resources', 'peers-p2p.json')));
            fs_1.copyFileSync(peersApiFile, path_1.join(path_1.join(brokerConfig, 'resources', 'peers-api.json')));
        }
        await new VotingService_1.VotingService(this.logger, this.params).run(presetData, account, nodePreset, currentFinalizationEpoch, undefined, ConfigLoader_1.ConfigLoader.shouldCreateNemesis(presetData));
    }
    async generateP2PFile(knownPeers, listLimit, outputFolder, info, jsonFileName) {
        const data = {
            _info: info,
            knownPeers: _.sampleSize(knownPeers, listLimit),
        };
        const peerFile = path_1.join(outputFolder, `resources`, jsonFileName);
        await fs.promises.writeFile(peerFile, JSON.stringify(data, null, 2));
        await fs.promises.chmod(peerFile, 0o600);
        return peerFile;
    }
    async generateNemesisConfig(presetData, addresses) {
        if (!presetData.nemesis) {
            throw new Error('nemesis must not be defined!');
        }
        const target = this.params.target;
        const nemesisWorkingDir = BootstrapUtils_1.BootstrapUtils.getTargetNemesisFolder(target, false);
        const transactionsDirectory = path_1.join(nemesisWorkingDir, presetData.nemesis.transactionsDirectory || presetData.transactionsDirectory);
        await BootstrapUtils_1.BootstrapUtils.mkdir(transactionsDirectory);
        const copyFrom = path_1.join(BootstrapUtils_1.BootstrapUtils.ROOT_FOLDER, `config`, `nemesis`);
        const moveTo = path_1.join(nemesisWorkingDir, `server-config`);
        const templateContext = Object.assign(Object.assign({}, presetData), { addresses });
        const nodes = (addresses.nodes || []).filter((n, index) => { var _a, _b; return !((_b = (_a = presetData.nodes) === null || _a === void 0 ? void 0 : _a[index]) === null || _b === void 0 ? void 0 : _b.excludeFromNemesis); });
        await Promise.all(nodes.filter((n) => n.vrf).map((n) => this.createVrfTransaction(transactionsDirectory, presetData, n)));
        await Promise.all(nodes.filter((n) => n.remote).map((n) => this.createAccountKeyLinkTransaction(transactionsDirectory, presetData, n)));
        await Promise.all(nodes.map((n) => this.createVotingKeyTransactions(transactionsDirectory, presetData, n)));
        if (presetData.nemesis.transactions) {
            const transactionHashes = [];
            const transactions = (await Promise.all(Object.entries(presetData.nemesis.transactions || {})
                .map(([key, payload]) => {
                const transactionHash = symbol_sdk_1.Transaction.createTransactionHash(payload, Array.from(symbol_sdk_1.Convert.hexToUint8(presetData.nemesisGenerationHashSeed)));
                if (transactionHashes.indexOf(transactionHash) > -1) {
                    this.logger.warn(`Transaction ${key} wth hash ${transactionHash} already exist. Excluded from folder.`);
                    return undefined;
                }
                transactionHashes.push(transactionHash);
                return this.storeTransaction(transactionsDirectory, key, payload);
            })
                .filter((p) => p))).filter((p) => p);
            this.logger.info(`Found ${transactions.length} provided in transactions.`);
        }
        await BootstrapUtils_1.BootstrapUtils.generateConfiguration(templateContext, copyFrom, moveTo);
        await new NemgenService_1.NemgenService(this.logger, this.params).run(presetData);
    }
    async createVrfTransaction(transactionsDirectory, presetData, node) {
        if (!node.vrf) {
            throw new Error('VRF keys should have been generated!!');
        }
        if (!node.main) {
            throw new Error('Main keys should have been generated!!');
        }
        const deadline = symbol_sdk_1.Deadline.createFromDTO('1');
        const vrf = symbol_sdk_1.VrfKeyLinkTransaction.create(deadline, node.vrf.publicKey, symbol_sdk_1.LinkAction.Link, presetData.networkType, symbol_sdk_1.UInt64.fromUint(0));
        const mainPrivateKey = await CommandUtils_1.CommandUtils.resolvePrivateKey(this.logger, presetData.networkType, node.main, KeyName.Main, node.name, 'creating the vrf key link transactions');
        const account = symbol_sdk_1.Account.createFromPrivateKey(mainPrivateKey, presetData.networkType);
        const signedTransaction = account.sign(vrf, presetData.nemesisGenerationHashSeed);
        return this.storeTransaction(transactionsDirectory, `vrf_${node.name}`, signedTransaction.payload);
    }
    async createAccountKeyLinkTransaction(transactionsDirectory, presetData, node) {
        if (!node.remote) {
            throw new Error('Remote keys should have been generated!!');
        }
        if (!node.main) {
            throw new Error('Main keys should have been generated!!');
        }
        const deadline = symbol_sdk_1.Deadline.createFromDTO('1');
        const akl = symbol_sdk_1.AccountKeyLinkTransaction.create(deadline, node.remote.publicKey, symbol_sdk_1.LinkAction.Link, presetData.networkType, symbol_sdk_1.UInt64.fromUint(0));
        const mainPrivateKey = await CommandUtils_1.CommandUtils.resolvePrivateKey(this.logger, presetData.networkType, node.main, KeyName.Main, node.name, 'creating the account link transactions');
        const account = symbol_sdk_1.Account.createFromPrivateKey(mainPrivateKey, presetData.networkType);
        const signedTransaction = account.sign(akl, presetData.nemesisGenerationHashSeed);
        return this.storeTransaction(transactionsDirectory, `remote_${node.name}`, signedTransaction.payload);
    }
    async createVotingKeyTransactions(transactionsDirectory, presetData, node) {
        const votingFiles = node.voting || [];
        const mainPrivateKey = await CommandUtils_1.CommandUtils.resolvePrivateKey(this.logger, presetData.networkType, node.main, KeyName.Main, node.name, 'creating the voting key link transactions');
        return Promise.all(votingFiles.map(async (votingFile) => {
            const voting = symbol_sdk_1.VotingKeyLinkTransaction.create(symbol_sdk_1.Deadline.createFromDTO('1'), votingFile.publicKey, votingFile.startEpoch, votingFile.endEpoch, symbol_sdk_1.LinkAction.Link, presetData.networkType, 1, symbol_sdk_1.UInt64.fromUint(0));
            const account = symbol_sdk_1.Account.createFromPrivateKey(mainPrivateKey, presetData.networkType);
            const signedTransaction = account.sign(voting, presetData.nemesisGenerationHashSeed);
            return this.storeTransaction(transactionsDirectory, `voting_${node.name}`, signedTransaction.payload);
        }));
    }
    async storeTransaction(transactionsDirectory, name, payload) {
        const transaction = symbol_sdk_1.TransactionMapping.createFromPayload(payload);
        await fs.promises.writeFile(`${transactionsDirectory}/${name}.bin`, symbol_sdk_1.Convert.hexToUint8(payload));
        return transaction;
    }
    generateGateways(presetData) {
        return Promise.all((presetData.gateways || []).map(async (gatewayPreset, index) => {
            const copyFrom = path_1.join(BootstrapUtils_1.BootstrapUtils.ROOT_FOLDER, 'config', 'rest-gateway');
            const generatedContext = {
                restDeploymentToolVersion: BootstrapUtils_1.BootstrapUtils.VERSION,
                restDeploymentToolLastUpdatedDate: new Date().toISOString().slice(0, 10),
            };
            const templateContext = Object.assign(Object.assign(Object.assign({}, generatedContext), presetData), gatewayPreset);
            const name = templateContext.name || `rest-gateway-${index}`;
            const moveTo = BootstrapUtils_1.BootstrapUtils.getTargetGatewayFolder(this.params.target, false, name);
            await BootstrapUtils_1.BootstrapUtils.generateConfiguration(templateContext, copyFrom, moveTo);
            const apiNodeConfigFolder = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(this.params.target, false, gatewayPreset.apiNodeName, 'server-config', 'resources');
            const apiNodeCertFolder = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(this.params.target, false, gatewayPreset.apiNodeName, 'cert');
            await BootstrapUtils_1.BootstrapUtils.generateConfiguration({}, apiNodeConfigFolder, path_1.join(moveTo, 'api-node-config'), [], ['config-network.properties', 'config-node.properties']);
            await BootstrapUtils_1.BootstrapUtils.generateConfiguration({}, apiNodeCertFolder, path_1.join(moveTo, 'api-node-config', 'cert'), [], ['node.crt.pem', 'node.key.pem', 'ca.cert.pem']);
            if (gatewayPreset.restProtocol === 'HTTPS') {
                if (gatewayPreset.restSSLKeyBase64 && gatewayPreset.restSSLCertificateBase64) {
                    fs.writeFileSync(path_1.join(moveTo, presetData.restSSLKeyFileName), gatewayPreset.restSSLKeyBase64, 'base64');
                    fs.writeFileSync(path_1.join(moveTo, presetData.restSSLCertificateFileName), gatewayPreset.restSSLCertificateBase64, 'base64');
                }
                else {
                    if (!fs_1.existsSync(path_1.join(moveTo, presetData.restSSLKeyFileName)) &&
                        !fs_1.existsSync(path_1.join(moveTo, presetData.restSSLCertificateFileName))) {
                        throw new BootstrapUtils_1.KnownError(`Native SSL is enabled but restSSLKeyBase64 or restSSLCertificateBase64 properties are not found in the custom-preset file! Either use 'symbol-bootstrap wizard' command to fill those properties in the custom-preset or make sure you copy your SSL key and cert files to ${moveTo} folder.`);
                    }
                    else {
                        this.logger.info(`Native SSL certificates for gateway ${gatewayPreset.name} have been previously provided. Reusing...`);
                    }
                }
            }
        }));
    }
    resolveCurrencyName(presetData) {
        var _a, _b;
        const mosaicPreset = (_b = (_a = presetData.nemesis) === null || _a === void 0 ? void 0 : _a.mosaics) === null || _b === void 0 ? void 0 : _b[0];
        const currencyName = mosaicPreset === null || mosaicPreset === void 0 ? void 0 : mosaicPreset.name;
        if (!currencyName) {
            throw new Error('Currency name could not be resolved!!');
        }
        return currencyName;
    }
    generateExplorers(presetData, remoteNodeService) {
        return Promise.all((presetData.explorers || []).map(async (explorerPreset, index) => {
            const copyFrom = path_1.join(BootstrapUtils_1.BootstrapUtils.ROOT_FOLDER, 'config', 'explorer');
            const fullName = `${presetData.baseNamespace}.${this.resolveCurrencyName(presetData)}`;
            const namespaceId = new symbol_sdk_1.NamespaceId(fullName);
            const { restNodes, defaultNode } = await this.resolveRests(presetData, remoteNodeService);
            const templateContext = Object.assign(Object.assign({ namespaceName: fullName, namespaceId: namespaceId.toHex(), restNodes: restNodes, defaultNode: defaultNode }, presetData), explorerPreset);
            const name = templateContext.name || `explorer-${index}`;
            const moveTo = BootstrapUtils_1.BootstrapUtils.getTargetFolder(this.params.target, false, BootstrapUtils_1.BootstrapUtils.targetExplorersFolder, name);
            await BootstrapUtils_1.BootstrapUtils.generateConfiguration(templateContext, copyFrom, moveTo);
        }));
    }
    async resolveRests(presetData, remoteNodeService) {
        var _a;
        const restNodes = [];
        (_a = presetData.gateways) === null || _a === void 0 ? void 0 : _a.forEach((restService) => {
            var _a;
            const nodePreset = (_a = presetData.nodes) === null || _a === void 0 ? void 0 : _a.find((g) => g.name == restService.apiNodeName);
            restNodes.push(`http://${restService.host || (nodePreset === null || nodePreset === void 0 ? void 0 : nodePreset.host) || 'localhost'}:3000`);
        });
        restNodes.push(...(await remoteNodeService.getRestUrls()));
        return { restNodes: _.uniq(restNodes), defaultNode: restNodes[0] || 'http://localhost:3000' };
    }
    cleanUpConfiguration(presetData) {
        const target = this.params.target;
        (presetData.nodes || []).forEach(({ name }) => {
            const serverConfigFolder = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(target, false, name, 'server-config');
            BootstrapUtils_1.BootstrapUtils.deleteFolder(this.logger, serverConfigFolder);
            const brokerConfigFolder = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(target, false, name, 'broker-config');
            BootstrapUtils_1.BootstrapUtils.deleteFolder(this.logger, brokerConfigFolder);
            // Remove old user configs when upgrading.
            const userConfigFolder = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(target, false, name, 'userconfig');
            BootstrapUtils_1.BootstrapUtils.deleteFolder(this.logger, userConfigFolder);
            const seedFolder = BootstrapUtils_1.BootstrapUtils.getTargetNodesFolder(target, false, name, 'seed');
            BootstrapUtils_1.BootstrapUtils.deleteFolder(this.logger, seedFolder);
        });
        (presetData.gateways || []).forEach(({ name }) => {
            const configFolder = BootstrapUtils_1.BootstrapUtils.getTargetGatewayFolder(target, false, name);
            BootstrapUtils_1.BootstrapUtils.deleteFolder(this.logger, configFolder, [
                path_1.join(configFolder, presetData.restSSLKeyFileName),
                path_1.join(configFolder, presetData.restSSLCertificateFileName),
            ]);
        });
    }
}
exports.ConfigService = ConfigService;
ConfigService.defaultParams = {
    target: BootstrapUtils_1.BootstrapUtils.defaultTargetFolder,
    workingDir: BootstrapUtils_1.BootstrapUtils.defaultWorkingDir,
    report: false,
    offline: false,
    reset: false,
    upgrade: false,
    user: BootstrapUtils_1.BootstrapUtils.CURRENT_USER,
};
