const invariant = require("tiny-invariant")
const Web3 = require("web3")
const web3 = new Web3()
const { OutboundProofType, NETWORKS, EVMUtilityVersion } = require("./constants")
const proofUtilsV1 = require("./proofUtilsV1")
const proofUtilsV2 = require("./proofUtilsV2")
const { ethers } = require("ethers")

async function getReceiptProof(network, block, transactionReceipts, transactionIndex, outboundProofType, utilsVersion) {
    invariant(NETWORKS.includes(network), `Unsupported network: ${network}`)
    switch (outboundProofType) {
        case OutboundProofType.MPT: {
            switch (utilsVersion) {
                case EVMUtilityVersion.V1: {
                    return await proofUtilsV1.getMPTReceiptProof(network, block, transactionReceipts, transactionIndex)
                }
                case EVMUtilityVersion.V2:
                case EVMUtilityVersion.V4: {
                    return await proofUtilsV2.getMPTReceiptProof(network, block, transactionReceipts, transactionIndex)
                }
                case EVMUtilityVersion.V3: {
                    const proof = await proofUtilsV2.getMPTReceiptProof(network, block, transactionReceipts, transactionIndex)
                    proof["blockHash"] = block.hash
                    return proof
                }
                default:
                    invariant(false, `Unknown utility version ${utilsVersion}`)
            }
            break
        }
        default:
            invariant(false, `Unsupported Outbound Proof Type ${outboundProofType}`)
    }
}

function encodeParams(proof, outboundProofType, utilsVersion, logIndex, srcEndpointId = undefined) {
    switch (outboundProofType) {
        case OutboundProofType.MPT: {
            switch (utilsVersion) {
                case EVMUtilityVersion.V1: {
                    return web3.eth.abi.encodeParameters(
                        ["uint16", "bytes[]", "uint256[]", "uint256", "uint256"],
                        [srcEndpointId, proof.proof, proof.pointers, proof.receiptSlotIndex, logIndex]
                    )
                }
                case EVMUtilityVersion.V2: {
                    return web3.eth.abi.encodeParameters(
                        ["uint16", "bytes[]", "uint256[]", "uint256"],
                        [srcEndpointId, proof.proof, proof.receiptSlotIndex, logIndex]
                    )
                }
                case EVMUtilityVersion.V3: {
                    return web3.eth.abi.encodeParameters(
                        ["uint16", "bytes32", "bytes[]", "uint256[]", "uint256"],
                        [srcEndpointId, proof.blockHash, proof.proof, proof.receiptSlotIndex, logIndex]
                    )
                }
                case EVMUtilityVersion.V4: {
                    return web3.eth.abi.encodeParameters(["bytes[]", "uint256[]", "uint256"], [proof.proof, proof.receiptSlotIndex, logIndex])
                }
                default:
                    invariant(false, `Unknown utility version ${utilsVersion}`)
            }
            break
        }
        default:
            invariant(false, `Unsupported Outbound Proof Type ${outboundProofType}`)
    }
}

function getLayerZeroPacket(srcChainId, _log) {
    //todo: what if src address differs from 20 bytes
    const packet = web3.eth.abi.decodeParameters(["uint16", "bytes"], _log.data)
    const data = packet[1].slice(2)

    //todo: what if dst address differs from 20 bytes
    const dstChainId = parseInt(packet[0])
    const nonce = ethers.BigNumber.from("0x" + data.slice(0, 16)).toNumber()
    const srcAddress = "0x" + data.slice(16, 56)
    const dstAddress = "0x" + data.slice(56, 96)

    let payload = "0x"
    if (data.length > 96) {
        //has payload
        payload = "0x" + data.slice(96, data.length)
    }

    return {
        srcChainId,
        dstChainId,
        nonce,
        dstAddress,
        srcAddress,
        ulnAddress: web3.eth.abi.encodeParameter("bytes32", _log.address),
        payload,
    }
}

module.exports = {
    getReceiptProof,
    encodeParams,
    getLayerZeroPacket,
}
