const inquirer = require('inquirer');
const { toBN, toWei } = require('web3-utils')

global.serviceController = {
    error: "",
    deposit: async (args) => {
        if (args) {

            if (!args.shifter) {
                console.log("No shifter specified");
                return;
            }

            let { shifter } = args;
            let shifterInstance = cryptoController.shifters[shifter];

            let password = helpers.randomHex(32);
            
            depositObject = await serviceController.prepareDeposit(shifter, cryptoController.sender, password);
            await shifterInstance.methods.deposit(helpers.toFixedHex(depositObject.commitment), depositObject.encryptedNote, depositObject.passwordHash)
                .send({ from: cryptoController.sender, gasLimit: config.gasLimit, value: await shifterInstance.methods.ethDenomination().call() })
            await sqlite.saveNote({ note: depositObject.saveNote, shifter: shifter });

            console.log(`Deposit successful.`);

            await cryptoController.refBalance(shifter);

        } else {
            let args = {};

            let allShifters = cryptoController.shifterList;
            let shifterChoices = [];
            shifterChoices.push(new inquirer.Separator(" "));
            for (let i = 0; i < allShifters.length; i++) {
                let shifterInstance = cryptoController.shifters[allShifters[i]];
                let shifterToken = cryptoController.shifterTokens[allShifters[i]];
                let tokenName = await cryptoController.tokens[shifterToken].methods.name().call();
                let tokenDenomination = await shifterInstance.methods.denomination().call();
                shifterChoices.push(
                    {
                        value: allShifters[i],
                        name: `${tokenName} - ${helpers.fromWei(tokenDenomination)} (${allShifters[i]})`,
                    });
            }

            shifterChoices.push(new inquirer.Separator(" "));
            
            shifterChoices.push({
                value: "exit",
                name: "Exit",
            });
            shifterChoices.push(new inquirer.Separator(" "));

            let answers = await inquirer.prompt({
                type: 'list',
                name: 'shifter',
                message: 'Select a shifter:',
                choices: shifterChoices
            })

            if (answers.shifter == "exit") return;

            args = { shifter: answers.shifter }
            await serviceController.deposit(args);
        }
    },
    simpleShift: async (args) => {
        if (args) {

            if (!args.shifter) {
                console.log("No shifter specified");
                return;
            }

            if (!args.amount) {
                console.log("No amount specified");
                return;
            }

            let shifterInstance = cryptoController.shifters[args.shifter];
            let { amount, shifter } = args;

            let shifterToken = await cryptoController.shifters[shifter].methods.token().call();
            let tokenInstance = cryptoController.tokens[shifterToken];
            const tokenBalance = await tokenInstance.methods.balanceOf(cryptoController.sender).call();

            if (toBN(tokenBalance) < (toBN(amount))) {
                errorMsg = "Insufficient balance";
                serviceController.error = errorMsg;
                console.log(errorMsg);
                return;
            }

            console.log(`Shifting ${amount} ${await tokenInstance.methods.symbol().call()} to ${cryptoController.sender}.`)

            await shifterInstance.methods.simpleShift(amount, cryptoController.sender).send({ from: cryptoController.sender, gasLimit: config.gasLimit })

            await cryptoController.refBalance(args.shifter);
        } else {
            let args = {};

            let allTokens = Object.keys(cryptoController.tokens);
            let tokenShifters = {};

            for (let i = 0; i < allTokens.length; i++) {
                for (let j = 0; j < cryptoController.shifterList.length; j++) {
                    let thisShifter = cryptoController.shifters[cryptoController.shifterList[j]];
                    let thisToken = await thisShifter.methods.token().call();
                    if (thisToken === allTokens[i]) {
                        tokenShifters[allTokens[i]] = {
                            shifter: cryptoController.shifterList[j],
                            name: await cryptoController.tokens[allTokens[i]].methods.name().call(),
                        }
                        break;
                    }
                }
            }


            let shifterChoices = [];
            for (let i = 0; i < Object.keys(tokenShifters).length; i++) {
                shifterChoices.push({
                    value: tokenShifters[Object.keys(tokenShifters)[i]].shifter,
                    name: tokenShifters[Object.keys(tokenShifters)[i]].name,
                })
            }

            shifterChoices.push({
                value: "Exit",
                name: "Exit"
            });

            let answers = await inquirer.prompt({
                type: 'list',
                name: 'shifter',
                message: 'Select a token:',
                pageSize: shifterChoices.length,
                choices: shifterChoices
            })

            if (answers.shifter === "Exit") return;

            let amountPrompt = await inquirer.prompt({
                type: 'input',
                name: 'amount',
                message: 'Enter amount to shift:',
            })

            if (!isNaN(amountPrompt.amount)) {
                let errorMsg = "No amount specified";
                serviceController.error = errorMsg;
                console.log(errorMsg);
                return;
            }

            args = { shifter: answers.shifter, amount: toWei(amountPrompt.amount) }
            await serviceController.simpleShift(args); 
        }

    },
    withdraw: async (args) => {
        if (args) {

            if (!args.shifter) {
                let errorMsg = "No shifter specified";
                serviceController.error = errorMsg;
                console.log(errorMsg);
                return;
            }

            if (!args.note) {
                let errorMsg = "No note specified";
                serviceController.error = errorMsg;
                console.log(errorMsg);
                return;
            }

            if (!args.address) {
                let errorMsg = "No address specified";
                serviceController.error = errorMsg;
                console.log(errorMsg);
                return;
            }

            let { note, address, shifter } = args;

            let shifterInstance = cryptoController.shifters[shifter];

            const { proof, withdrawArgs } = await cryptoController.generateProof(shifterInstance, address, note);
            console.log("Withdrawing aUSD.")

            let relayerEndpoint = config.relayUrl + "/relay";

            const transaction = await fetch(relayerEndpoint, { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ proof: proof, shifter: shifter, args: withdrawArgs }) })

            depositObject = null;
            await cryptoController.refBalance(shifter);

        } else {
            let args = {};

            let allNotes = await sqlite.fetchNotes();
            let noteChoices = [];

            
            for (let i = 0; i < allNotes.length; i++) {

                let shifterInstance = cryptoController.shifters[allNotes[i].shifter];
                let shifterToken = cryptoController.shifterTokens[allNotes[i].shifter];
                let tokenName = await cryptoController.tokens[shifterToken].methods.name().call();
                let tokenDenomination = await shifterInstance.methods.denomination().call();
                
                let parsed = cryptoController.parseSaveNote(allNotes[i].note);
                isSpent = await shifterInstance.methods.isSpent(parsed.nullifierHex).call();

                if (isSpent) {
                    await sqlite.spentNote(allNotes[i].note);
                    continue;
                }

                let parsedNote = cryptoController.parseSaveNote(allNotes[i].note);
                let noteHex = parsedNote.commitmentHex.slice(0, 6) + "..." + parsedNote.commitmentHex.slice(-6);
                allNotes[i].hex = noteHex;
                allNotes[i].display = `${noteHex} (${tokenName} ${helpers.fromWei(tokenDenomination)})`
                noteChoices.push(allNotes[i]);
            }

            noteChoices = noteChoices.map((note) => { return { name: note.display, value: note } })

            noteChoices.push(new inquirer.Separator(" "));
            noteChoices.push({
                value: "exit",
                name: "Exit",
            });
            noteChoices.push(new inquirer.Separator(" "));

            let answers = await inquirer.prompt({
                type: 'list',
                name: 'note',
                message: 'Select a note:',
                choices: noteChoices
            })

            if (answers.note == "exit") return;

            let withdrawalAddress = await inquirer.prompt({
                type: 'input',
                name: 'address',
                message: 'Enter address to withdraw to:',
            })

            
            if (!helpers.isAddress(withdrawalAddress.address)) {
                let errorMsg = "Invalid address specified";
                serviceController.error = errorMsg;
                console.log(errorMsg);
                return;
            }
            
            args = { note: answers.note.note, shifter: answers.note.shifter, address: withdrawalAddress.address }

            await serviceController.withdraw(args);
        }
    },
    viewBalances: async () => {
        let allTokens = Object.keys(cryptoController.tokens);
        let balances = [];

        await cryptoController.refBalance();
        
        for (let i = 0; i < allTokens.length; i++) {
            let token = allTokens[i]; 
            let tokenName = await cryptoController.tokens[token].methods.name().call();
            let tokenBalance = await cryptoController.tokens[token].methods.balanceOf(cryptoController.sender).call();
            balances.push({ "name": `${tokenName} - ${helpers.fromWei(tokenBalance)}`, "value": tokenBalance });
        };

        balances.push({
            value: "exit",
            name: "Exit",
        });
        
        await inquirer.prompt({
            type: 'list',
            name: 'balance',
            message: 'Token Balances:',
            choices: balances
        })

        return;
    },
    prepareDeposit: async (shifter, address, password) => {
        depositObject = {
            secret: helpers.rbigint(31),
            nullifier: helpers.rbigint(31)
        }

        const saveNote = `0x${depositObject.nullifier.toString(16, 'hex').padStart(62, '0')}${depositObject.secret.toString(16, 'hex').padStart(62, '0')}`;
        const preimage = Buffer.concat([depositObject.nullifier.leInt2Buff(31), depositObject.secret.leInt2Buff(31)])

        depositObject.commitment = helpers.pedersenHash(preimage);
        depositObject.buffNullifierHash = helpers.pedersenHash(depositObject.nullifier.leInt2Buff(31));
        depositObject.saveNote = saveNote
        
        depositObject.encryptedNote = await cryptoController.xcrypt(saveNote, password, shifter, address, null);
        depositObject.passwordHash = helpers.keccak256(password);

        return depositObject
    },
    getAggregateDeposits: async (shifters, address, decrypted) => {
        const password = cryptoController.getOrSetPasswordLocally(); 
        let encryptedNotes = [];
        let decryptedNotes = [];
        let spentNotes = [];
        for (let _shifter in shifters) {
            shifter = shifters[_shifter];
            if (password) { 
                let passwordHash = helpers.keccak256(password)
                let shifterNotes = await xftAnon.methods.getDeposits(shifter, address, passwordHash);
                shifterNotes = shifterNotes.map((note) => { return { "note": note, "shifter": shifter } });
                try {
                    encryptedNotes = encryptedNotes.concat(shifterNotes);
                } catch (e) { console.log(e) };
            } else {
                throw ("No password set");
            }
        }
        let nullHashArray;
        let boolSpentArray = [];
        let decryptedUnspentNotes;
        if (decrypted) {
            for (let idx in encryptedNotes) {
                let thisNote = encryptedNotes[idx];
                let thisDecryptedNote = (await cryptoController.xcrypt(thisNote.note, password));
                let deposited = await serviceController._getShifterContract(thisNote.shifter).methods.commitments(parseSaveNote(thisDecryptedNote).commitmentHex).call()
                let spent = await serviceController._getShifterContract(thisNote.shifter).methods.isSpent(parseSaveNote(thisDecryptedNote).nullifierHex).call();
                
                if (!spent && deposited)
                    decryptedNotes.push({
                        "note": thisDecryptedNote,
                        "shifter": thisNote.shifter
                    })
                
                else if (spent && deposited)
                    spentNotes.push({
                        "note": thisDecryptedNote,
                        "shifter": thisNote.shifter
                    })
            }
            decryptedNotes = decryptedNotes
                .map((note, idx) => {
                    return {
                        "note": note.note,
                        "shifter": note.shifter,
                        "commitment": parseSaveNote(note.note).commitmentHex
                    }
                });
            spentNotes = spentNotes
                .map((note, idx) => {
                    return {
                        "note": note.note,
                        "shifter": note.shifter,
                        "commitment": parseSaveNote(note.note).commitmentHex
                    }
                });
        }
        return (decrypted ? { decryptedNotes, spentNotes } : encryptedNotes);
    }
}