import { exec, execSync } from "child_process";
import { newBarretenbergApiSync } from "@aztec/bb.js/dest/node/index.js";
import { Fr } from "@aztec/bb.js/dest/node/types/index.js";
import inquirer from "inquirer";

import {
    generateUTXO,
    generateDataToml,
    generateTestTransaction,
    generateTreeProof,
    generateTestPublish,
    randomBytesFr,
    readToml,
    getSolidityHash,
    treeConfig,
    getInputs,
    getInputsOrdered,
    getPublicInputsOrdered,
} from "./utils/helpers.js";
import { MerkleTree } from "./utils/MerkleTree.mjs";
import fs from "fs";
import { ethers, JsonRpcProvider, ContractFactory } from "ethers";
import PrivateTransferData from "../deps/TechnicalPreview.json" assert { type: "json" };
import { Sequelize, DataTypes, Model } from "sequelize";
import "dotenv/config";

class UTXOdb extends Model {
    secret;
    amount;
    assetType;
    spent;
}

let sequelize = new Sequelize({
    dialect: "sqlite",
    storage: "txs.db",
    logging: false,
});

UTXOdb.init(
    {
        secret: {
            type: DataTypes.STRING,
            allowNull: false,
        },
        amount: {
            type: DataTypes.STRING,
            allowNull: false,
        },
        assetType: {
            type: DataTypes.INTEGER,
        },
        spent: {
            type: DataTypes.BOOLEAN,
        },
        id: {
            type: DataTypes.STRING,
            allowNull: false,
            primaryKey: true,
        },
    },
    {
        sequelize: sequelize,
        tableName: "UTXOs",
    }
);


await sequelize.sync();


const fetchUTXOs = async () =>
    await UTXOdb.findAll({ where: { spent: false } });
const fetchSpentUTXOs = async () =>
    await UTXOdb.findAll({ where: { spent: true } });
const spendUTXO = async (UTXO) =>
    await UTXOdb.update({ spent: true }, { where: { id: UTXO } });
const saveUTXO = async (UTXO) => {
    await UTXOdb.upsert({
        secret: UTXO.secret.toString(),
        amount: UTXO.amount.toString(),
        assetType: 0,
        id: UTXO.id.toString(),
        spent: false,
    });
};

const provider = new JsonRpcProvider(process.env.PROVIDER);

const privateKey = process.env.PRIVATE_KEY;
const wallet = new ethers.Wallet(privateKey, provider);

const technicalPreviewContract = new ethers.Contract(
    "0x8162d56A21D0ee799Eef055C3acC2b4F776f693a",
    PrivateTransferData.abi,
    wallet
);

export async function balance() {
    const api = await newBarretenbergApiSync();
    let utxos = await fetchUTXOs();

    
    let nullifierArray = [];
    for (let i = 0; i < utxos.length; i++) {
        nullifierArray.push(api.pedersenPlookupCompress([
            Fr.fromString(utxos[i].dataValues.secret),
            Fr.fromString(utxos[i].dataValues.secret)
        ]).toString());
    }
    let spentArray = await technicalPreviewContract.getSpentNullifiers(nullifierArray);
    
    for (let i = 0; i < spentArray.length; i++) {
        if (spentArray[i] === true) {
            await spendUTXO(utxos[i].dataValues.id);
            utxos.splice(i, 1);
        }
    }

    if (utxos.length === 0) return ethers.formatEther(BigInt(0));
    else {
        let utxosBigInt = utxos.map((res) => BigInt(res.dataValues.amount));
        return ethers.formatEther(utxosBigInt.reduce((a, b) => a + b));
    }
}
export const deposit = async (args) => {
    console.log(`** Compiling Transaction Circuit **`);

    execSync(`nargo check`);
    execSync(`nargo compile`);
    let recipient, recipient_new;
    const api = await newBarretenbergApiSync();

    let trees = {
        utxo_tree: new MerkleTree(4, api),
        tx_tree: new MerkleTree(4, api),
        historic_tree: new MerkleTree(9, api),
        utxoTreeOld: new MerkleTree(4, api),
        txTreeOld: new MerkleTree(4, api),
        newHistoricRoot: "",
    };
    let utxoIn = [];
    let utxoOut = [];
    let treeProof = [];
    let validRoots = await technicalPreviewContract.getValidRoots();
    
    
    let oldRoot, newRoot;
    if (validRoots.length === 1) {
        oldRoot = validRoots[0];
        newRoot = validRoots[0];
    } else {
        oldRoot = validRoots[validRoots.length - 2];
        newRoot = validRoots[validRoots.length - 1];
    }
    let data = generateDataToml(oldRoot, newRoot, trees, api);
    let amountPublic = {
        amountIn: BigInt(0),
        amountOut: BigInt(0),
    };

    recipient = `0x` + `dEaD`.padStart(64, "0");

    console.log("** Populating UTXO tree.. **");

    console.log("** Generating a batch of zkXFT UTXOs... **");

    let batchSize0 = 1;
    let secret0 = [];
    for (let s = 0; s < batchSize0; s++) {
        secret0.push(randomBytesFr(32));
    }
    let amountsOutUTXO = new Array(batchSize0).fill(BigInt(0));
    amountsOutUTXO[0] = BigInt(args.amount);
    utxoOut = generateUTXO(batchSize0, amountsOutUTXO, secret0, api);

    amountPublic.amountIn = BigInt(args.amount);

    generateTestTransaction(
        utxoIn,
        utxoOut,
        trees,
        treeProof,
        amountPublic,
        data,
        recipient,
        api,
        technicalPreviewContract
    );
    console.log("** Creating Deposit and Publisher Proof... **");
    generateTestPublish(trees, data, api);

    execSync(`nargo prove`);

    let proof = fs.readFileSync("./proofs/main.proof").toString();
    proof = `0x` + proof;

    let public_inputs = readToml("./Verifier.toml");
    let tx = await technicalPreviewContract.publish(proof, public_inputs, {
        value: 0, 
    });
    utxoIn = utxoOut;

    let txReceipt = await provider.getTransactionReceipt(tx.hash);
    while (txReceipt === null) {
        txReceipt = await provider.getTransactionReceipt(tx.hash);
    }

    let utxoId = api.pedersenPlookupCompress([
        utxoIn[0].owner,
        utxoIn[0].amount,
        utxoIn[0].assetType,
    ]);

    utxoOut[0].id = utxoId;

    await saveUTXO(utxoOut[0]);
    return [trees, data];
};

export const transact = async (args) => {
    const api = await newBarretenbergApiSync();
    
    let amount = BigInt(args.amount);
    let trees = {
        utxo_tree: new MerkleTree(4, api),
        tx_tree: new MerkleTree(4, api),
        historic_tree: new MerkleTree(9, api),
        utxoTreeOld: new MerkleTree(4, api),
        txTreeOld: new MerkleTree(4, api),
        newHistoricRoot: "",
    };

    console.log("** Populating Historic tree.. **");
    
    let validRoots = await technicalPreviewContract.getValidRoots();
    for (let i = 0; i < validRoots.length; i++) {
        trees.historic_tree.insert(validRoots[i]);
    }

    let spending = [];
    let spendingSum = BigInt(0);
    
    let utxo_leaves = (await fetchUTXOs()).map((utxo) => {
        let utxo_out = {
            secret: Fr.fromString(utxo.dataValues.secret),
            owner: api.pedersenPlookupCompress([
                Fr.fromString(utxo.dataValues.secret),
            ]),
            amount: Fr.fromString(utxo.dataValues.amount),
            assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")),
            spent: utxo.dataValues.spent,
            id: utxo.dataValues.id,
        };
        return utxo_out;
    });

    
    let utxoOut = [];
    let changeUTXO = {
        secret: Fr.fromString("0x00"),
        owner: api.pedersenPlookupCompress([Fr.fromString("0x00")]),
        amount: Fr.fromString("0x00"),
        assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")),
    };
    for (let i = 0; i < utxo_leaves.length; i++) {
        if (spendingSum < amount) {
            spending.push(utxo_leaves[i]);
            spendingSum += BigInt(utxo_leaves[i].amount.toString());
        }
        
        if (spendingSum > amount) {
            let change = spendingSum - amount;
            let secret = randomBytesFr(32);
            changeUTXO = {
                secret: secret,
                owner: api.pedersenPlookupCompress([secret]),
                amount: new Fr(change),
                assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), "hex")),
            };
            changeUTXO.id = api.pedersenPlookupCompress([
                changeUTXO.owner,
                changeUTXO.amount,
                changeUTXO.assetType,
            ]);
            utxoOut.push(changeUTXO);
            break;
        }
    }

    
    let treeProofs = [];
    for (let i = 0; i < spending.length; i++) {
        let proofForUtxo = await generateTreeProof(
            spending[i],
            api,
            technicalPreviewContract
        );
        treeProofs.push(proofForUtxo);
    }

    let batchSize1 = 1;

    
    
    let oldRoot, newRoot;
    if (validRoots.length === 1) {
        oldRoot = validRoots[0];
        newRoot = validRoots[0];
    } else {
        oldRoot = validRoots[validRoots.length - 2];
        newRoot = validRoots[validRoots.length - 1];
    }

    let data = generateDataToml(oldRoot, newRoot, trees, api);

    let amountPublic = {
        amountIn: BigInt(0),
        amountOut: BigInt(args.amount),
    };

    
    let recipientPrompt = await inquirer.prompt({
        type: "input",
        name: "recipient",
        message: "Enter the destination address: ",
    });

    let recipient_new =
        `0x` + `${recipientPrompt.recipient.replace("0x", "")}`.padStart(64, "0");

    await generateTestTransaction(
        spending,
        utxoOut,
        trees,
        treeProofs,
        amountPublic,
        data,
        recipient_new,
        api,
        technicalPreviewContract
    );

    generateTestPublish(trees, data, api);

    console.log(`** Compiling Transaction Circuit **`);

    execSync(`nargo check`);
    execSync(`nargo compile`);
    console.log("** Generating transaction Parameters... **");

    execSync(`nargo prove`);

    let proof = fs.readFileSync("./proofs/main.proof").toString();
    proof = `0x` + proof;

    let public_inputs = readToml("./Verifier.toml");

    let tx = await technicalPreviewContract.publish(proof, public_inputs, {
        value: 0, 
    });

    
    let txReceipt = await provider.getTransactionReceipt(tx.hash);
    while (txReceipt === null) {
        console.log("Waiting for tx confirmation...")
        txReceipt = await provider.getTransactionReceipt(tx.hash);
    }

    for (let i = 0; i < spending.length; i++) {
        spendUTXO(spending[i].id);
    }
    
    await saveUTXO(changeUTXO);
};