"use strict";
/*
 * Copyright 2020 NEM
 *
 * 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.LinkService = void 0;
const inquirer_1 = require("inquirer");
const symbol_sdk_1 = require("symbol-sdk");
const logger_1 = require("../logger");
const LoggerFactory_1 = require("../logger/LoggerFactory");
const AnnounceService_1 = require("./AnnounceService");
const BootstrapUtils_1 = require("./BootstrapUtils");
const ConfigLoader_1 = require("./ConfigLoader");
const logger = LoggerFactory_1.default.getLogger(logger_1.LogType.System);
class LinkService {
    constructor(params) {
        this.params = params;
        this.configLoader = new ConfigLoader_1.ConfigLoader();
    }
    async run(passedPresetData, passedAddresses) {
        const presetData = passedPresetData !== null && passedPresetData !== void 0 ? passedPresetData : this.configLoader.loadExistingPresetData(this.params.target, this.params.password);
        const addresses = passedAddresses !== null && passedAddresses !== void 0 ? passedAddresses : this.configLoader.loadExistingAddresses(this.params.target, this.params.password);
        logger.info(`${this.params.unlink ? 'Unlinking' : 'Linking'} nodes`);
        await new AnnounceService_1.AnnounceService().announce(this.params.url, this.params.maxFee, this.params.useKnownRestGateways, this.params.ready, presetData, addresses, this);
    }
    async createTransactions({ presetData, nodeAccount, mainAccountInfo, deadline, maxFee, removeOldLinked, }) {
        var _a;
        const transactions = [];
        const unlink = this.params.unlink;
        const networkType = presetData.networkType;
        logger.info('');
        logger.info(`Creating transactions for node: ${nodeAccount.name}, ca/main account: ${mainAccountInfo.address.plain()}`);
        if (nodeAccount.remote) {
            const accountTobeLinked = nodeAccount.remote;
            const alreadyLinkedAccount = mainAccountInfo.supplementalPublicKeys.linked;
            const isAlreadyLinkedSameAccount = (alreadyLinkedAccount === null || alreadyLinkedAccount === void 0 ? void 0 : alreadyLinkedAccount.publicKey.toUpperCase()) === accountTobeLinked.publicKey.toUpperCase();
            await this.addTransaction(alreadyLinkedAccount, isAlreadyLinkedSameAccount, unlink, ({ publicKey }, action) => symbol_sdk_1.AccountKeyLinkTransaction.create(deadline, publicKey, action, networkType, maxFee), nodeAccount, 'Remote', nodeAccount.remote, transactions, removeOldLinked, (account) => `public key ${account.publicKey}`);
        }
        if (nodeAccount.vrf) {
            const accountTobeLinked = nodeAccount.vrf;
            const alreadyLinkedAccount = mainAccountInfo.supplementalPublicKeys.vrf;
            const isAlreadyLinkedSameAccount = (alreadyLinkedAccount === null || alreadyLinkedAccount === void 0 ? void 0 : alreadyLinkedAccount.publicKey.toUpperCase()) === accountTobeLinked.publicKey.toUpperCase();
            await this.addTransaction(alreadyLinkedAccount, isAlreadyLinkedSameAccount, unlink, ({ publicKey }, action) => symbol_sdk_1.VrfKeyLinkTransaction.create(deadline, publicKey, action, networkType, maxFee), nodeAccount, 'VRF', accountTobeLinked, transactions, removeOldLinked, (account) => `public key ${account.publicKey}`);
        }
        if (nodeAccount.voting) {
            const accountTobeLinked = {
                publicKey: nodeAccount.voting.publicKey,
                startEpoch: presetData.votingKeyStartEpoch,
                endEpoch: presetData.votingKeyEndEpoch,
            };
            const alreadyLinkedAccount = (((_a = mainAccountInfo.supplementalPublicKeys) === null || _a === void 0 ? void 0 : _a.voting) || []).find((a) => LinkService.overlapsVotingAccounts(accountTobeLinked, a));
            const isAlreadyLinkedSameAccount = (alreadyLinkedAccount === null || alreadyLinkedAccount === void 0 ? void 0 : alreadyLinkedAccount.publicKey.toUpperCase()) === accountTobeLinked.publicKey.toUpperCase() &&
                (alreadyLinkedAccount === null || alreadyLinkedAccount === void 0 ? void 0 : alreadyLinkedAccount.startEpoch) === accountTobeLinked.startEpoch &&
                (alreadyLinkedAccount === null || alreadyLinkedAccount === void 0 ? void 0 : alreadyLinkedAccount.endEpoch) === accountTobeLinked.endEpoch;
            await this.addTransaction(alreadyLinkedAccount, isAlreadyLinkedSameAccount, unlink, (votingKeyAccount, action) => {
                return symbol_sdk_1.VotingKeyLinkTransaction.create(deadline, votingKeyAccount.publicKey, votingKeyAccount.startEpoch, votingKeyAccount.endEpoch, action, presetData.networkType, 1, maxFee);
            }, nodeAccount, 'Voting', accountTobeLinked, transactions, removeOldLinked, (account) => `public key ${account.publicKey}, start epoch ${account.startEpoch}, end epoch ${account.endEpoch}`);
        }
        return transactions;
    }
    static overlapsVotingAccounts(x, y) {
        return x.endEpoch >= y.startEpoch && x.startEpoch <= y.endEpoch;
    }
    async addTransaction(alreadyLinkedAccount, isAlreadyLinkedSameAccount, unlink, transactionFactory, nodeAccount, accountName, accountTobeLinked, transactions, removeOldLinked, print) {
        if (unlink) {
            if (alreadyLinkedAccount) {
                if (isAlreadyLinkedSameAccount) {
                    const transaction = transactionFactory(accountTobeLinked, symbol_sdk_1.LinkAction.Unlink);
                    logger.info(`Creating Unlink ${transaction.constructor.name} for node ${nodeAccount.name} to ${accountName} ${print(accountTobeLinked)}.`);
                    transactions.push(transaction);
                }
                else {
                    logger.warn(`Node ${nodeAccount.name} is linked to a different ${accountName} ${print(alreadyLinkedAccount)} and not the configured ${print(accountTobeLinked)}.`);
                    if (await this.confirmUnlink(removeOldLinked, accountName, alreadyLinkedAccount, print)) {
                        const transaction = transactionFactory(alreadyLinkedAccount, symbol_sdk_1.LinkAction.Unlink);
                        logger.info(`Creating Unlink ${transaction.constructor.name} for node ${nodeAccount.name} to ${accountName} ${print(alreadyLinkedAccount)}.`);
                        transactions.push(transaction);
                    }
                }
            }
            else {
                logger.info(`Node ${nodeAccount.name} is not linked to ${accountName} ${print(accountTobeLinked)}.`);
                return;
            }
        }
        else {
            if (alreadyLinkedAccount) {
                if (isAlreadyLinkedSameAccount) {
                    logger.info(`Node ${nodeAccount.name} is already linked to ${accountName} ${print(alreadyLinkedAccount)}.`);
                }
                else {
                    logger.warn(`Node ${nodeAccount.name} is already linked to ${accountName} ${print(alreadyLinkedAccount)} which is different from the configured ${print(accountTobeLinked)}.`);
                    if (await this.confirmUnlink(removeOldLinked, accountName, alreadyLinkedAccount, print)) {
                        const unlinkTransaction = transactionFactory(alreadyLinkedAccount, symbol_sdk_1.LinkAction.Unlink);
                        logger.info(`Creating Unlink ${unlinkTransaction.constructor.name} from Node ${nodeAccount.name} to ${accountName} ${print(alreadyLinkedAccount)}.`);
                        transactions.push(unlinkTransaction);
                        const linkTransaction = transactionFactory(accountTobeLinked, symbol_sdk_1.LinkAction.Link);
                        logger.info(`Creating Link ${linkTransaction.constructor.name} from Node ${nodeAccount.name} to ${accountName} ${print(accountTobeLinked)}.`);
                        transactions.push(linkTransaction);
                    }
                }
            }
            else {
                const transaction = transactionFactory(accountTobeLinked, symbol_sdk_1.LinkAction.Link);
                logger.info(`Creating Link ${transaction.constructor.name} from Node ${nodeAccount.name} to ${accountName} ${print(accountTobeLinked)}.`);
                transactions.push(transaction);
            }
        }
    }
    async confirmUnlink(removeOldLinked, accountName, alreadyLinkedAccount, print) {
        if (removeOldLinked === undefined) {
            return (this.params.ready ||
                (await inquirer_1.prompt([
                    {
                        name: 'value',
                        message: `Do you want to unlink the old ${accountName} ${print(alreadyLinkedAccount)}?`,
                        type: 'confirm',
                        default: false,
                    },
                ])).value);
        }
        return removeOldLinked;
    }
}
exports.LinkService = LinkService;
LinkService.defaultParams = {
    target: BootstrapUtils_1.BootstrapUtils.defaultTargetFolder,
    useKnownRestGateways: false,
    ready: false,
    url: 'http://localhost:3000',
    maxFee: 100000,
    unlink: false,
};
