import pkg from 'hardhat';
const { ethers } = pkg;
const { provider } = ethers;
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
import { newBarretenbergApiSync } from '@aztec/bb.js/dest/index.js';
import { BigNumber } from "ethers";
import { expect } from "chai";
import path from 'path';
import { execSync } from 'child_process';
import { MerkleTree } from "../utils/MerkleTree.mjs";
import { generateUTXO, generateTestTransaction, generateTestPublish, generateDataToml, randomBytesFr, generateTreeProof } from '../utils/test_helpers.mjs';
import fs from "fs"
describe('It compiles circuit.', () => {
  let verifierContract;
  before(async () => {
    execSync(`nargo check`)
    execSync(`nargo compile main`)
    if (!fs.existsSync('./contracts/plonk_vk.sol')) {
      execSync(`nargo codegen-verifier && cp ./contract/plonk_vk.sol ./contracts/plonk_vk.sol`)
    }
  });
  before('Deploy contract', async () => {
    console.log('Deploying verifier contract...')
    
    execSync('npx hardhat compile', { cwd: path.join(__dirname, '../contracts') });
    const Verifier = await ethers.getContractFactory('UltraVerifier');
    verifierContract = await Verifier.deploy();
    const verifierAddr = await verifierContract.deployed();
    console.log(`Verifier deployed to ${verifierAddr.address}`);
  });
});

let signers;
let recipient, recipient_new;
const api = await newBarretenbergApiSync();
let trees = {
  utxo_tree: new MerkleTree(4, api),
  tx_tree: new MerkleTree(4, api),
  batch_tree: new MerkleTree(5, api),
  historic_tree: new MerkleTree(5, api),
  utxoTreeOld: new MerkleTree(4, api),
  txTreeOld: new MerkleTree(4, api),
  batchLeaf: "",
  newHistoricRoot: ""
}
let utxoIn = []
let utxoOut = []
let treeProof = [] 
let data = generateDataToml("0", "0", api) 
let amountPublic = {
  amountIn: BigInt(0),
  amountOut: BigInt(0)
}
before(async () => {
  signers = await ethers.getSigners();
  recipient = signers[1].address;
  recipient = `0x`+ recipient.slice(2).padStart(64, "0")
  recipient_new = signers[2].address;
  recipient_new = `0x` + recipient_new.slice(2).padStart(64, "0")
});
describe("Private Transfer works with Solidity verifier", () => {
  let Verifier, verifierContract, TechnicalPreview, technicalPreviewContract;
  before("Set up Verifier contract", async () => {
    TechnicalPreview = await ethers.getContractFactory("TechnicalPreview");
    Verifier = await ethers.getContractFactory("UltraVerifier");
    verifierContract = await Verifier.deploy();
    technicalPreviewContract = await TechnicalPreview.deploy(verifierContract.address);
  })
  it("Deposit works using Solidity verifier", async () => {
    console.log("** Generating Test Batch **")
    let batchSize0 = 10
    let secret0 = []
    for (let s = 0; s < batchSize0; s++) {
        secret0.push(randomBytesFr(32))
    }
    let amountsOutUTXO = new Array(10).fill(BigInt(1e17))
    utxoOut = generateUTXO(batchSize0, amountsOutUTXO, secret0, api);
    
    amountPublic.amountIn = BigInt(1e18)
    generateTestTransaction(utxoIn, utxoOut, trees, treeProof, amountPublic, data, recipient, api)
    console.log("** Generating Transaction Proof #1 (Deposit) **")
    generateTestPublish(trees, data, api)
    execSync(`nargo prove main`)
    let proof = fs.readFileSync('./proofs/main.proof').toString()
    proof = `0x`+ proof
    let  amount_public_in,
    amount_public_out,
    commitment_out,
    new_root,
    nullifier_hashes,
    old_root,
    oracle,
    
    tx_in;
    eval(fs.readFileSync('./Verifier.toml').toString());
    let public_inputs = [ 
        tx_in,
        amount_public_in,
        amount_public_out,
        commitment_out,
        recipient,
        oracle,
        old_root,
        new_root,
        nullifier_hashes
    ]
    const before = await provider.getBalance(signers[1].address);
    const technicalPreviewSigner = technicalPreviewContract.connect(signers[1]);
    await technicalPreviewSigner.publish(proof, public_inputs, {value: amount_public_in, gasPrice: '0'});
    const after = await provider.getBalance(signers[1].address);
    
    expect(before.sub(after)).to.equal(BigNumber.from(amount_public_in));
    utxoIn = utxoOut;
    
  });
  it("Private transfer works using Solidity verifier", async () => {
    console.log("** Generating Transaction Proof #2 (Withdraw/Transfer) **")
    let amountsOutUTXO = new Array(5).fill(BigInt(1e17))
    
    treeProof = generateTreeProof(utxoIn, trees, api)
    
    let batchSize1 = 5
    let secret1 = []
    for (let s = 0; s < batchSize1; s++) {
        secret1.push(randomBytesFr(32))
    }
    utxoOut = generateUTXO(batchSize1, amountsOutUTXO, secret1, api);
    amountPublic.amountIn = BigInt(0);
    amountPublic.amountOut = BigInt(5e17);
    
    let oldRoot = data.old_root
    let newRoot = data.new_root
    data = generateDataToml(oldRoot, newRoot, api)
    const before = await provider.getBalance(signers[2].address);
    generateTestTransaction(utxoIn, utxoOut, trees, treeProof, amountPublic, data, recipient_new, api)
    
    generateTestPublish(trees, data, api)
    execSync(`nargo prove main`)
    
    let proof = fs.readFileSync('./proofs/main.proof').toString()
    proof = `0x`+ proof
    let  amount_public_in,
    amount_public_out,
    commitment_out,
    new_root,
    nullifier_hashes,
    old_root,
    oracle,
    
    tx_in;
    eval(fs.readFileSync('./Verifier.toml').toString());
    let public_inputs = [ 
        tx_in,
        amount_public_in,
        amount_public_out,
        commitment_out,
        recipient_new,
        oracle,
        old_root,
        new_root,
        nullifier_hashes
    ]
  
    await technicalPreviewContract.publish(proof, public_inputs)
    const after = await provider.getBalance(signers[2].address);
    expect(after.sub(before)).to.equal(BigNumber.from(amount_public_out));
  });
});