
// readParameters provides functions to read and verify parameters

package readParameters


import "seekia/internal/cryptography/blake3"
import "seekia/internal/cryptography/edwardsKeys"
import "seekia/internal/helpers"
import "seekia/internal/encoding"

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

func VerifyParametersType(parametersType string)bool{

	allParametersTypesList := GetAllParametersTypesList()

	isValid := slices.Contains(allParametersTypesList, parametersType)
	if (isValid == true){
		return true
	}

	return false	
}

func VerifyParametersHash(inputHash [31]byte)bool{

	metadataByte := inputHash[30]

	if (metadataByte != 1){
		return false
	}

	return true
}

func GetAllParametersTypesList()[]string{

	//TODO: Add more

	typesList := []string{"AdminPermissions", "GeneralParameters"}

	return typesList
}

func VerifyParameters(inputParameters []byte)(bool, error){

	ableToRead, _, _, _, _, _, _, err := ReadParameters(true, inputParameters)
	if (err != nil) { return false, err }
	if (ableToRead == false){
		return false, nil
	}

 	return true, nil
}

//Outputs:
//	-bool: Parameters are valid
//	-[31]byte: Parameters Hash
//	-int: Parameters version
//	-byte: Parameters network type byte (1 == Mainnet, 2 == Testnet1)
//	-[][32]byte: List of admins who signed the parameters
//	-string: Parameters type
//	-int64: Parameters creation time
//	-map[string]string: Parameters Map
//	-error (will return error if there is a bug in the function)
func ReadParametersAndHash(verifyParameters bool, inputParameters []byte)(bool, [31]byte, int, byte, [][32]byte, string, int64, map[string]string, error){

	ableToRead, parametersVersion, parametersNetworkType, parametersAuthorsList, parametersType, parametersCreationTime, parametersMap, err := ReadParameters(verifyParameters, inputParameters)
	if (err != nil) { return false, [31]byte{}, 0, 0, nil, "", 0, nil, err }
	if (ableToRead == false){
		return false, [31]byte{}, 0, 0, nil, "", 0, nil, nil
	}

	parametersHashWithoutMetadataByte, err := blake3.GetBlake3HashAsBytes(30, inputParameters)
	if (err != nil) { return false, [31]byte{}, 0, 0, nil, "", 0, nil, err }

	//TODO: Use this byte for something useful
	metadataByte := byte(1)

	parametersHashSlice := append(parametersHashWithoutMetadataByte, metadataByte)

	parametersHash := [31]byte(parametersHashSlice)

	return true, parametersHash, parametersVersion, parametersNetworkType, parametersAuthorsList, parametersType, parametersCreationTime, parametersMap, nil

}

// This function reads the parameters without computing the hash, thus saving computational time.
//Outputs:
//	-bool: Parameters are valid
//	-int: Parameters version
//	-byte: Parameters network type byte (1 == Mainnet, 2 == Testnet1)
//	-[][32]byte: List of admins who signed the parameters
//	-string: Parameters type
//	-int64: Parameters creation time
//	-map[string]string: Parameters Map
//	-error (will return error if there is a bug in the function)
func ReadParameters(verifyParameters bool, inputParameters []byte)(bool, int, byte, [][32]byte, string, int64, map[string]string, error){

	var parametersSlice []messagepack.RawMessage

	err := encoding.DecodeMessagePackBytes(false, inputParameters, &parametersSlice)
	if (err != nil){
		// Invalid parameters: Invalid messagepack
		return false, 0, 0, nil, "", 0, nil, nil
	}

	if (len(parametersSlice) != 2){
		// Invalid parameters: Invalid messagepack
		return false, 0, 0, nil, "", 0, nil, nil
	}

	parametersSignaturesEncoded := parametersSlice[0]
	parametersContentMessagepack := parametersSlice[1]

	var adminSignaturesList [][64]byte

	err = encoding.DecodeMessagePackBytes(false, parametersSignaturesEncoded, &adminSignaturesList)
	if (err != nil){
		// Invalid parameters: Invalid parameters signatures
		return false, 0, 0, nil, "", 0, nil, nil
	}

	rawParametersMap := make(map[int]messagepack.RawMessage)

	err = encoding.DecodeMessagePackBytes(false, parametersContentMessagepack, &rawParametersMap)
	if (err != nil){
		// Invalid parameters: Invalid content
		return false, 0, 0, nil, "", 0, nil, nil
	}

	parametersVersionEncoded, exists := rawParametersMap[1]
	if (exists == false){
		// Invalid parameters: Missing parameters version
		return false, 0, 0, nil, "", 0, nil, nil
	}

	parametersVersion, err := encoding.DecodeRawMessagePackToInt(parametersVersionEncoded)
	if (err != nil){
		// Invalid parameters: Contains invalid parameters version
		return false, 0, 0, nil, "", 0, nil, nil
	}

	if (parametersVersion != 1){

		// Parameters must have been created by a different Seekia version. We cannot read them.
		return false, 0, 0, nil, "", 0, nil, nil
	}

	networkTypeEncoded, exists := rawParametersMap[2]
	if (exists == false){
		// Invalid parameters: Missing network type
		return false, 0, 0, nil, "", 0, nil, nil
	}

	networkTypeByte, err := encoding.DecodeRawMessagePackToByte(networkTypeEncoded)
	if (err != nil){
		// Invalid parameters: Contains invalid network type
		return false, 0, 0, nil, "", 0, nil, nil
	}

	isValid := helpers.VerifyNetworkType(networkTypeByte)
	if (isValid == false){
		// Invalid parameters: Contains invalid network type
		return false, 0, 0, nil, "", 0, nil, nil
	}

	adminPublicKeysListEncoded, exists := rawParametersMap[3]
	if (exists == false){
		// Invalid parameters: Missing admin public keys list
		return false, 0, 0, nil, "", 0, nil, nil
	}

	var adminPublicKeysList [][32]byte

	err = encoding.DecodeMessagePackBytes(false, adminPublicKeysListEncoded, &adminPublicKeysList)
	if (err != nil){
		// Invalid parameters: Invalid admin public keys list
		return false, 0, 0, nil, "", 0, nil, nil
	}

	if (len(adminPublicKeysList) == 0){
		return false, 0, 0, nil, "", 0, nil, nil
	}

	if (verifyParameters == true){

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

		if (len(adminSignaturesList) != len(adminPublicKeysList)){
			// Invalid parameters: Admin signatures do not match admin public keys.
			return false, 0, 0, nil, "", 0, nil, nil
		}

		for index, adminPublicKey := range adminPublicKeysList{

			if (len(adminPublicKey) != 32){
				// Invalid parameters: Invalid admin public key
				return false, 0, 0, nil, "", 0, nil, nil
			}

			keySignature := adminSignaturesList[index]

			signatureIsValid := edwardsKeys.VerifySignature(adminPublicKey, keySignature, contentHash)
			if (signatureIsValid == false){
				// Cannot read parameters: Signature is invalid
				return false, 0, 0, nil, "", 0, nil, nil
			}
		}
	}

	creationTimeEncoded, exists := rawParametersMap[4]
	if (exists == false){
		// Invalid parameters: Missing CreationTime
		return false, 0, 0, nil, "", 0, nil, nil
	}

	creationTime, err := encoding.DecodeRawMessagePackToInt64(creationTimeEncoded)
	if (err != nil){
		// Invalid parameters: Contains invalid CreationTime
		return false, 0, 0, nil, "", 0, nil, nil
	}

	parametersTypeEncoded, exists := rawParametersMap[5]
	if (exists == false){
		// Invalid parameters: Missing ParametersType
		return false, 0, 0, nil, "", 0, nil, nil
	}

	parametersType, err := encoding.DecodeRawMessagePackToString(parametersTypeEncoded)
	if (err != nil){
		// Invalid parameters: Invalid ParametersType
		return false, 0, 0, nil, "", 0, nil, nil
	}

	if (verifyParameters == true){

		isValid := helpers.VerifyCreationTime(creationTime)
		if (isValid == false){
			// Invalid parameters: Invalid CreationTime
			return false, 0, 0, nil, "", 0, nil, nil
		}

		isValid = VerifyParametersType(parametersType)
		if (isValid == false){
			// Invalid parameters: Invalid ParametersType
			return false, 0, 0, nil, "", 0, nil, nil
		}
	}

	networkTypeString := helpers.ConvertByteToString(networkTypeByte)

	creationTimeString := helpers.ConvertInt64ToString(creationTime)

	adminPublicKeyStringsList := make([]string, 0, len(adminPublicKeysList))

	for _, adminPublicKeyBytes := range adminPublicKeysList{

		adminPublicKeyString := encoding.EncodeBytesToHexString(adminPublicKeyBytes[:])

		adminPublicKeyStringsList = append(adminPublicKeyStringsList, adminPublicKeyString)
	}

	adminPublicKeysJoined := strings.Join(adminPublicKeyStringsList, "+")

	parametersMap := map[string]string{
		"ParametersVersion": "1",
		"NetworkType": networkTypeString,
		"ParametersType": parametersType,
		"CreationTime": creationTimeString,
		"AdminPublicKeys": adminPublicKeysJoined,
	}

	//TODO: Add more

	return true, parametersVersion, networkTypeByte, adminPublicKeysList, parametersType, creationTime, parametersMap, nil
}



