import { randomBytes } from 'crypto'
import { readFileSync } from 'fs';
import { Fr } from '@aztec/bb.js/dest/types/index.js';
import { MerkleTree } from './MerkleTree.mjs';
import { keccak256 } from "@ethersproject/keccak256/lib/index.js";
import fs from 'fs'

export function randomBytesFr(numBytes) {
  const bytes = randomBytes(numBytes)
  const bytesFr = Fr.fromBufferReduce(bytes)
  return bytesFr
}

export const format = (data) => {
    if (data.length === 0) return "[]"
    return `[\n    "${data.join('",\n    "')}"\n]`;
}

export const dumpToml = (data) => {
    data =
    `
            tx_in = ${format(data.tx_in)}
            amount_public_in = "${data.amount_public_in}"
            amount_public_out = "${data.amount_public_out}"
            commitment_out = ${format(data.commitment_out)}
            recipient = "${data.recipient}"
            oracle = "${data.oracle}"
            old_root = "${data.old_root}"
            new_root = "${data.new_root}"
            secrets = ${format(data.secrets)}
            utxo_in = ${format(data.utxo_in)}
            utxo_out = ${format(data.utxo_out)}
            roots = ${format(data.roots)}
            leaves = ${format(data.leaves)}
            indexes = ${format(data.indexes)}
            hash_path = ${format(data.hash_path)}
            nullifier_hashes = ${format(data.nullifiers)}
    `
    fs.writeFileSync('./Prover.toml', data);
}

export function path_to_uint8array(path) {
    let buffer = readFileSync(path);
    return new Uint8Array(buffer);
  }
  
const toFixedHex = (number, pad0x, length = 32) => {
  let hexString = number.toString(16).padStart(length * 2, '0');
  return (pad0x ? `0x` + hexString : hexString);
}

export function getSolidityHash(asset) {
  // Flatten the object
  asset = 0;
  return keccak256(asset);
}
  
export function generateHashPathInput(hash_path) {
  let hash_path_input = [];
  for (var i = 0; i < hash_path.length; i++) {
    hash_path_input.push(`0x` + hash_path[i]);
  }
  return hash_path_input;
}

export function generateUTXO(batchSize, amounts, _secrets, BarretenbergApi) {
  let utxos = []
  for (let i = 0; i < batchSize; i++) {
    let amountBN = amounts[i]
    let utxo = {
      secret: _secrets[i],
      owner: BarretenbergApi.pedersenPlookupCompress([_secrets[i]]),
      amountBN: amountBN,
      amount: Fr.fromString(toFixedHex(Number(amountBN.toString()), true)),
      assetType: Fr.fromBufferReduce(Buffer.from(getSolidityHash(0), 'hex')),
    }
    utxos.push(utxo)
  }
  return utxos
}

export function generateTreeProof(utxoIn, trees, BarretenbergApi) {
  let treeProof = []
  for (let i = 0; i < utxoIn.length; i++) {
    let commitment = BarretenbergApi.pedersenPlookupCompress(
      [utxoIn[i].owner, utxoIn[i].amount, utxoIn[i].assetType]
    ).toString()
    let proofs = {
        utxo: {
            leaf: commitment,
            index: trees.utxoTreeOld.getIndex(commitment),
            root: trees.utxoTreeOld.root(),
            hash_path: trees.utxoTreeOld.proof(
              trees.utxoTreeOld.getIndex(commitment)
            ).pathElements
        },
        tx: {
            leaf: trees.utxoTreeOld.root(),
            index: trees.txTreeOld.getIndex(trees.utxoTreeOld.root()),
            root: trees.txTreeOld.root(),
            hash_path: trees.txTreeOld.proof(
              trees.txTreeOld.getIndex(trees.utxoTreeOld.root())
            ).pathElements
        },
        batch: {
            leaf: trees.batchLeaf,
            index: trees.batch_tree.getIndex(trees.batchLeaf),
            root: trees.batch_tree.root(),
            hash_path: trees.batch_tree.proof(
              trees.batch_tree.getIndex(trees.batchLeaf)
            ).pathElements
        },
        historic: {
            leaf: trees.newHistoricRoot,
            index: trees.historic_tree.getIndex(trees.newHistoricRoot),
            root: trees.historic_tree.root(),
            hash_path: trees.historic_tree.proof(
              trees.historic_tree.getIndex(trees.newHistoricRoot)
            ).pathElements
        }
      }
    treeProof.push(proofs)
  }
  return treeProof
}

export function generateDataToml(oldRoot, newRoot, api) {
  let zeroHash = api.pedersenPlookupCompress([Fr.fromString(toFixedHex(0, true))])

  const data = {
    tx_in: new Array(16).fill('0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725'),
    secrets: new Array(16).fill('0'),
    utxo_in: new Array(48).fill('0'),
    utxo_out: new Array(48).fill('0'),
    oracle: zeroHash.toString(),
    old_root: "0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725",
    new_root: "0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725",
    roots: new Array(64).fill('0'),
    leaves: new Array(64).fill('0'),
    indexes: new Array(64).fill('0'),
    hash_path: new Array(288).fill('0'),
    commitment_out: new Array(16).fill('0xf35fcb490b7ea67c3ac26ed530fa5d8dfe8be344e7177ebb63fe02723fb6f725'),
    amount_public_in: "0",
    amount_public_out: "0",
    nullifiers: new Array(16).fill('0'),
    recipient: Fr.fromString(toFixedHex(0, true)).toString()
  }

  if (oldRoot !== "0") data.old_root = oldRoot;
  if (newRoot !== "0") data.new_root = newRoot;

  return data

}

export function generateTestTransaction(utxoIn, utxoOut, trees, treeProof, amountPublic, data, recipient, BarretenbergApi) {
  let utxoInLen = utxoIn.length
  let utxoOutLen = utxoOut.length
  
  // UTXOs being spent
  for (let i = 0; i < utxoInLen; i++) {
    let ownerHex = utxoIn[i].owner.toString();
    let amountHex = utxoIn[i].amount.toString();
    let assetTypeHex = utxoIn[i].assetType.toString();
    
    let note_commitment = BarretenbergApi.pedersenPlookupCompress([utxoIn[i].owner, utxoIn[i].amount, utxoIn[i].assetType]);
    let utxoLeaf = note_commitment.toString()

    data.secrets[i] = utxoIn[i].secret
    data.nullifiers[i] = BarretenbergApi.pedersenPlookupCompressFields(utxoIn[i].secret, utxoIn[i].secret)

    data.utxo_in[i*3 + 0] = ownerHex
    data.utxo_in[i*3 + 1] = amountHex
    data.utxo_in[i*3 + 2] = assetTypeHex

    data.leaves[i*4 + 0] = utxoLeaf
    data.leaves[i*4 + 1] = treeProof[i].tx.leaf
    data.leaves[i*4 + 2] = treeProof[i].batch.leaf
    data.leaves[i*4 + 3] = treeProof[i].historic.leaf

    data.indexes[i*4 + 0] = treeProof[i].utxo.index
    data.indexes[i*4 + 1] = treeProof[i].tx.index
    data.indexes[i*4 + 2] = treeProof[i].batch.index
    data.indexes[i*4 + 3] = treeProof[i].historic.index

    data.roots[i*4 + 0] = treeProof[i].utxo.root
    data.roots[i*4 + 1] = treeProof[i].tx.root
    data.roots[i*4 + 2] = treeProof[i].batch.root
    data.roots[i*4 + 3] = treeProof[i].historic.root
    
    let utxoPath = treeProof[i].utxo.hash_path
    let txPath = treeProof[i].tx.hash_path
    let batchPath = treeProof[i].batch.hash_path
    let historicPath = treeProof[i].historic.hash_path

    for (let j = 0; j < utxoPath.length; j++) {
      data.hash_path[i*18 + 0 + j] = utxoPath[j]
      data.hash_path[i*18 + 4 + j] = txPath[j]
    }

    for (let k = 0; k < batchPath.length; k++) {
      data.hash_path[i*18 + 8 + k] = batchPath[k]
      data.hash_path[i*18 + 13 + k] = historicPath[k]
    }
  }
  
  // UTXOs being generated
  for (let i = 0; i < utxoOutLen; i++) {
    let ownerHex = utxoOut[i].owner.toString();
    let amountHex = utxoOut[i].amount.toString();
    let assetTypeHex = utxoOut[i].assetType.toString();
    
    let note_commitment = BarretenbergApi.pedersenPlookupCompress([utxoOut[i].owner, utxoOut[i].amount, utxoOut[i].assetType]);
    let utxoLeaf = note_commitment.toString()

    trees.utxo_tree.insert(utxoLeaf)

    data.utxo_out[i*3 + 0] = ownerHex
    data.utxo_out[i*3 + 1] = amountHex
    data.utxo_out[i*3 + 2] = assetTypeHex

    data.commitment_out[i] = utxoLeaf
  }
  data.tx_in[0] = trees.utxo_tree.root()
  data.amount_public_in = toFixedHex(Number(amountPublic.amountIn.toString()), true)
  data.amount_public_out = toFixedHex(Number(amountPublic.amountOut.toString()), true)
  data.recipient = recipient
  dumpToml(data)
}

export function generateTestPublish(trees, data, api) {
  // Publish our local set of test txs

  let utxoTree = trees.utxo_tree
  let txTree = trees.tx_tree
  let batchTree = trees.batch_tree
  let historicTree = trees.historic_tree

  let utxoRoot = utxoTree.root()
  txTree.insert(utxoRoot)
  let txRoot = txTree.root()
  let txRootFr = Fr.fromBufferReduce(Buffer.from(txRoot.slice(2), 'hex'))
  let oracleFr = Fr.fromBufferReduce(toFixedHex(0, true))
  let oracleHash = api.pedersenPlookupCompress([oracleFr])
  let batch = api.pedersenPlookupCompressFields(txRootFr, oracleHash)
  let batchHex = batch.toString()
  batchTree.insert(batchHex)

  let oldHistoricRoot = Fr.fromBufferReduce(Buffer.from(data.new_root.slice(2), 'hex'))
  let newHistoricRoot = api.pedersenPlookupCompress([batch, oldHistoricRoot])

  let newHistoricRootHex = newHistoricRoot.toString()
  historicTree.insert(newHistoricRootHex)

  data.old_root = oldHistoricRoot.toString()
  data.new_root = newHistoricRootHex

  // Clearing the utxo tree for the next batch
  // Saving the old tree for proofs
  trees.utxoTreeOld = trees.utxo_tree
  trees.txTreeOld = trees.tx_tree
  trees.batchLeaf = batchHex
  trees.newHistoricRoot = newHistoricRootHex

  trees.utxo_tree = new MerkleTree(4, api)
  trees.tx_tree = new MerkleTree(4, api)

  dumpToml(data)
}