// readPaymentProofs provides functions to read payment proofs

package readPaymentProofs

//TODO: Add size limits to payment proofs
//TODO: Restrict payment proofs to not allow moderator identities or non-mate profiles to be funded

import "seekia/internal/cryptocurrency/ethereumAddress"
import "seekia/internal/cryptography/blake3"
import "seekia/internal/encoding"
import "seekia/internal/helpers"

import messagepack "github.com/vmihailenco/msgpack/v5"

import "bytes"
import "slices"
import "math"
import "errors"

// Outputs:
//	-bool: Able to read payment proof
//	-[32]byte: Payment proof hash
//	-int: Payment proof version
//	-byte: Network type (1 == Mainnet, 2 == Testnet1)
//	-[]byte: The hash of the content/identity that is being paid for
//		-This is either an identity hash, a profile hash, a report hash, or a message hash
//	-string: The cryptocurrency that the proof was sent for
//		-For now, "Ethereum" is the only possible output
//	-string: The cryptocurrency address of the payment proof's tree root
//		-This is the address that funds should be sent to
//	-float64: The proportion of the entire amount sent to the payment proof's tree root address that this proof has paid for
//		-For example, if this value is 0.3, then 30% of the amount sent to the payment proof's merkle tree root cryptocurrency
//			address belongs to this payment proof.
//		-Another example: 10 ETH were sent to this address, 0.1 is the proportion this payment proof is designated for, thus, 1 ETH is
//			spent in this payment proof for the content/identity that is being paid for
//	-error
func ReadPaymentProofAndHash(inputPaymentProof []byte)(bool, [32]byte, int, byte, []byte, string, string, float64, error){ 

	ableToRead, paymentProofVersion, networkType, contentOrIdentityHash, cryptocurrencyUsed, cryptocurrencyAddress, proportionOfTotalDesignated, err := ReadPaymentProof(inputPaymentProof)
	if (err != nil) { return false, [32]byte{}, 0, 0, nil, "", "", 0, err }
	if (ableToRead == false){
		return false, [32]byte{}, 0, 0, nil, "", "", 0, nil
	}

	paymentProofHashWithoutMetadataByte, err := blake3.GetBlake3HashAsBytes(31, inputPaymentProof)
	if (err != nil) { return false, [32]byte{}, 0, 0, nil, "", "", 0, err }

	getPaymentProofHashMetadataByte := func()(byte, error){

		lengthOfContentOrIdentityHash := len(contentOrIdentityHash)

		switch lengthOfContentOrIdentityHash{

			case 16:{
				// Payment proof is paying for an identity
				return 1, nil
			}
			case 26:{
				// Payment proof is paying for a message
				return 2, nil
			}
			case 28:{
				// Payment proof is paying for a profile
				return 3, nil
			}
			case 30:{
				// Payment proof is paying for a report
				return 4, nil
			}
		}

		contentOrIdentityHashHex := encoding.EncodeBytesToHexString(contentOrIdentityHash)

		return 0, errors.New("ReadPaymentProof returning invalid length contentOrIdentityHash: " + contentOrIdentityHashHex)
	}

	paymentProofHashMetadataByte, err := getPaymentProofHashMetadataByte()
	if (err != nil) { return false, [32]byte{}, 0, 0, nil, "", "", 0, err }

	paymentProofHashSlice := append(paymentProofHashWithoutMetadataByte, paymentProofHashMetadataByte)

	paymentProofHash := [32]byte(paymentProofHashSlice)

	return true, paymentProofHash, paymentProofVersion, networkType, contentOrIdentityHash, cryptocurrencyUsed, cryptocurrencyAddress, proportionOfTotalDesignated, nil
}


// Outputs:
//	-bool: Able to read payment proof
//	-int: Payment proof version
//	-byte: Network type (1 == Mainnet, 2 == Testnet1)
//	-[]byte: The hash of the content/identity that is being paid for
//		-This is either an identity hash, a profile hash, a report hash, or a message hash
//	-string: The cryptocurrency that the proof was sent for
//		-For now, "Ethereum" is the only possible output
//	-string: The cryptocurrency address of the payment proof's tree root
//		-This is the address that funds should be sent to
//	-float64: (0-1) The proportion of the entire amount sent to the payment proof's tree root address that this proof has paid for
//		-For example, if this value is 0.3, then 30% of the amount sent to the payment proof's merkle tree root cryptocurrency
//			address belongs to this payment proof.
//		-Another example: 10 ETH were sent to this address, 0.1 is the proportion this payment proof is designated for, thus, 1 ETH is
//			spent in this payment proof for the content/identity that is being paid for
//	-error
func ReadPaymentProof(inputPaymentProof []byte)(bool, int, byte, []byte, string, string, float64, error){

	// Payment proofs are encoded in MessagePack
	// A payment proof is a MessagePack encoded list of the following items:
	//	-int: Payment proof version
	//	-byte: The network type for the payment proof
	//	-byte: The cryptocurrency that was used for the payment
	//		-1 == Ethereum
	//	-[]byte: The hash that is being paid for
	//	-[][32]byte: A list of nodes that comprise the merkle tree
	//		-Example: Node2, Node3, Node4, Node5...
	//		-The first Node1 is omitted, because we calculate its value by hashing (networkType + cryptocurrencyByte + contentOrIdentityHash)
	//		-Each node is the adjacent one to the parent nodes we are calculating
	//		-This slice can also only be empty
	//			-If this slice is empty, it means that there is no merkle tree. The payment proof is simply paying for 1 item in isolation.

	var paymentProofSlice []messagepack.RawMessage

	err := messagepack.Unmarshal(inputPaymentProof, &paymentProofSlice)
	if (err != nil){
		// Payment proof is malformed: Invalid payment proof messagepack
		return false, 0, 0, nil, "", "", 0, nil
	}

	if (len(paymentProofSlice) != 5){
		// Payment proof is malformed: Invalid payment proof messagepack
		return false, 0, 0, nil, "", "", 0, nil
	}

	paymentProofVersionEncoded := paymentProofSlice[0]
	paymentProofNetworkTypeEncoded := paymentProofSlice[1]
	paymentProofCryptocurrencyByteEncoded := paymentProofSlice[2]
	paymentProofContentOrIdentityHashEncoded := paymentProofSlice[3]
	paymentProofTreeNodesSliceEncoded := paymentProofSlice[4]

	paymentProofVersion, err := encoding.DecodeRawMessagePackToInt(paymentProofVersionEncoded)
	if (err != nil){
		// Payment proof is malformed: Invalid payment proof version
		return false, 0, 0, nil, "", "", 0, nil
	}

	if (paymentProofVersion != 1){
		// We cannot read this payment proof. It was created by an newer version of Seekia.
		return false, 0, 0, nil, "", "", 0, nil
	}

	paymentProofNetworkType, err := encoding.DecodeRawMessagePackToByte(paymentProofNetworkTypeEncoded)
	if (err != nil){
		// Payment proof is malformed: Invalid network type
		return false, 0, 0, nil, "", "", 0, nil
	}

	isValid := helpers.VerifyNetworkType(paymentProofNetworkType)
	if (isValid == false){
		// Payment proof is malformed: Invalid network type
		return false, 0, 0, nil, "", "", 0, nil
	}

	paymentProofCryptocurrencyByte, err := encoding.DecodeRawMessagePackToByte(paymentProofCryptocurrencyByteEncoded)
	if (err != nil){
		// Payment proof is malformed: Invalid cryptocurrency byte
		return false, 0, 0, nil, "", "", 0, nil
	}

	if (paymentProofCryptocurrencyByte != 1){
		// Payment proof is malformed: Invalid cryptocurrency byte
		return false, 0, 0, nil, "", "", 0, nil
	}

	paymentProofContentOrIdentityHash, err := encoding.DecodeRawMessagePackToBytes(paymentProofContentOrIdentityHashEncoded)
	if (err != nil){
		// Payment proof is malformed: Invalid content or identity hash
		return false, 0, 0, nil, "", "", 0, nil
	}

	contentOrIdentityHashLength := len(paymentProofContentOrIdentityHash)

	if (contentOrIdentityHashLength != 16 && contentOrIdentityHashLength != 26 && contentOrIdentityHashLength != 28 && contentOrIdentityHashLength != 30){
		// Payment proof is malformed: Invalid content or identity hash
		return false, 0, 0, nil, "", "", 0, nil
	}

	var paymentProofTreeNodesSlice [][32]byte

	err = encoding.DecodeMessagePackBytes(false, paymentProofTreeNodesSliceEncoded, &paymentProofTreeNodesSlice)
	if (err != nil) {
		// Payment proof is malformed: Invalid paymentProofTreeHashesSlice
		return false, 0, 0, nil, "", "", 0, nil
	}

	initialHashInput := []byte{paymentProofNetworkType}
	initialHashInput = append(initialHashInput, paymentProofCryptocurrencyByte)
	initialHashInput = append(initialHashInput, paymentProofContentOrIdentityHash...)

	initialHash, err := blake3.Get32ByteBlake3Hash(initialHashInput)
	if (err != nil) { return false, 0, 0, nil, "", "", 0, err }

	paymentProofTreeNodesSliceLength := len(paymentProofTreeNodesSlice)

	if (paymentProofTreeNodesSliceLength == 0){
		// There is no merkle tree
		// 100% of the funds belong to the payment proof's contentOrIdentityHash

		treeRootEthereumAddress, err := ethereumAddress.GetPaymentProofTreeRootEthereumAddress(initialHash)
		if (err != nil) { return false, 0, 0, nil, "", "", 0, err }

		return true, paymentProofVersion, paymentProofNetworkType, paymentProofContentOrIdentityHash, "Ethereum", treeRootEthereumAddress, 1, nil
	}

	currentNode := initialHash

	for _, treeNode := range paymentProofTreeNodesSlice{

		// We have to sort the nodes before we concatenate them
		// The order itself isn't important, just that it is always the same order

		getSortedNodes := func()([32]byte, [32]byte){

			result := bytes.Compare(currentNode[:], treeNode[:])

			if (result == 1){
				return currentNode, treeNode
			}

			return treeNode, currentNode
		}

		leftNode, rightNode := getSortedNodes()

		hashInput := slices.Concat(leftNode[:], rightNode[:])

		parentNode, err := blake3.Get32ByteBlake3Hash(hashInput)
		if (err != nil) { return false, 0, 0, nil, "", "", 0, err }

		currentNode = parentNode
	}

	treeRootEthereumAddress, err := ethereumAddress.GetPaymentProofTreeRootEthereumAddress(currentNode)
	if (err != nil) { return false, 0, 0, nil, "", "", 0, err }

	// Now we calculate the proportion of the total amount paid that went to the contentOrIdentity hash
	// This is accomplished by assessing which level of the tree the content's node exists on
	// Each level divides the total amount by 50%
	// Examples:
	// Layer 1 has 2 nodes, each node gets 50% of the total funds
	// Layer 2 has 4 nodes, each node gets 25% of the total funds
	// Layer 3 has 8 nodes, each node gets 12.5% of the total funds

	totalNumberOfNodesAtLayer := math.Pow(2, float64(paymentProofTreeNodesSliceLength))

	proportionOfFunds := float64(1)/float64(totalNumberOfNodesAtLayer)

	return true, paymentProofVersion, paymentProofNetworkType, paymentProofContentOrIdentityHash, "Ethereum", treeRootEthereumAddress, proportionOfFunds, nil
}


