
// parametersStorage provides functions to store and retrieve network parameters

package parametersStorage

//TODO: Create a job to prune old parameters

import "seekia/internal/parameters/readParameters"
import "seekia/internal/helpers"
import "seekia/internal/localFilesystem"
import "seekia/internal/encoding"

import "bytes"
import goFilepath "path/filepath"
import "strings"
import "errors"
import "slices"


//TODO: The key of the master admin who can change the admin permissions
const masterAdminPublicKey string = "TODO"

//Outputs:
//	-bool: Parameters to add are well formed
//	-bool: Admin permissions found (we cannot add parameters without these)
//	-error
func AddParameters(inputParameters []byte)(bool, bool, error){

	ableToRead, _, parametersNetworkType, listOfAdmins, parametersType, parametersCreationTime, _, err := readParameters.ReadParameters(true, inputParameters)
	if (err != nil) { return false, false, err }
	if (ableToRead == false){
		return false, false, nil
	}

	adminParametersFound, parametersAreAuthorized, err := verifyParametersAreAuthorized(parametersType, parametersNetworkType, parametersCreationTime, listOfAdmins)
	if (err != nil) { return false, false, err }
	if (adminParametersFound == false){
		// We do not have admin parameters. We cannot determine if parameters to add are authorized
		return false, false, nil
	}
	if (parametersAreAuthorized == false){
		// Parameters are not authorized.
		// Could be malicious host who sent parameters, or admin permissions were updated and we/they do not have them yet
		return true, true, nil
	}

	// We check if parameters already exist that are newer

	parametersFound, _, _, existingParametersCreationTime, err := GetAuthorizedParameters(parametersType, parametersNetworkType)
	if (err != nil) { return false, false, err }
	if (parametersFound == true){
		if (existingParametersCreationTime >= parametersCreationTime){
			// These parameters are older than our existing stored authorized parameters.
			// We will not add them.
			return true, true, nil
		}
	}

	seekiaDirectory, err := localFilesystem.GetSeekiaDataFolderPath()
	if (err != nil) { return false, false, err }

	networkTypeString := helpers.ConvertByteToString(parametersNetworkType)

	networkTypeFoldername := "Network" + networkTypeString

	parametersFolderpath := goFilepath.Join(seekiaDirectory, "Parameters", networkTypeFoldername)
	parametersFilename := parametersType + ".messagepack"

	err = localFilesystem.CreateOrOverwriteFile(inputParameters, parametersFolderpath, parametersFilename)
	if (err != nil) { return false, false, err }

	return true, true, nil
}


// This function will return parameters that are signed by known admins
//Outputs:
//	-bool: Requested parameters found and signed by valid admins
//	-[]byte: Parameters bytes
//	-map[string]string: Parameters map
//	-int64: Parameters creation time
//	-error
func GetAuthorizedParameters(parametersType string, networkType byte)(bool, []byte, map[string]string, int64, error){

	isValid := readParameters.VerifyParametersType(parametersType)
	if (isValid == false) {
		return false, nil, nil, 0, errors.New("GetAuthorizedParameters called with invalid parametersType: " + parametersType)
	}

	isValid = helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return false, nil, nil, 0, errors.New("GetAuthorizedParameters called with invalid networkType: " + networkTypeString)
	}

	parametersExist, storedParametersBytes, err := getStoredParameters(parametersType, networkType)
	if (err != nil) { return false, nil, nil, 0, err }
	if (parametersExist == false){
		return false, nil, nil, 0, nil
	}

	ableToRead, _, parametersNetworkType, listOfSigners, currentParametersType, parametersCreationTime, parametersMap, err := readParameters.ReadParameters(true, storedParametersBytes)
	if (err != nil) { return false, nil, nil, 0, err }
	if (ableToRead == false) {
		return false, nil, nil, 0, errors.New("Stored parameters are corrupt: storing corrupt " + parametersType + "parameter.")
	}
	if (parametersNetworkType != networkType){
		return false, nil, nil, 0, errors.New("getStoredParameters returning different networkType parameters")
	}
	if (currentParametersType != parametersType){
		return false, nil, nil, 0, errors.New("getStoredParameters returning different parametersType parameters")
	}

	adminPermissionsFound, parametersAreAuthorized, err := verifyParametersAreAuthorized(parametersType, networkType, parametersCreationTime, 	listOfSigners)
	if (err != nil) { return false, nil, nil, 0, err }
	if (adminPermissionsFound == false || parametersAreAuthorized == false){
		return false, nil, nil, 0, nil
	}

	return true, storedParametersBytes, parametersMap, parametersCreationTime, nil
}

//Outputs:
//	-bool: Admin permissions found
//	-bool: Parameters are authorized
//	-error
func verifyParametersAreAuthorized(parametersType string, networkType byte, parametersCreationTime int64, parametersSigners [][32]byte)(bool, bool, error){

	containsDuplicates, _ := helpers.CheckIfListContainsDuplicates(parametersSigners)
	if (containsDuplicates == true){
		return false, false, errors.New("verifyParametersAreAuthorized called with parametersSigners list containing duplicates.")
	}

	parametersExist, adminPermissionsParameters, err := getStoredParameters("AdminPermissions", networkType)
	if (err != nil) { return false, false, err }
	if (parametersExist == false){
		return false, false, nil
	}
	
	ableToRead, _, parametersNetworkType, listOfAdmins, currentParametersType, _, adminPermissionsMap, err := readParameters.ReadParameters(true, adminPermissionsParameters)
	if (err != nil) { return false, false, err }
	if (ableToRead == false){
		return false, false, errors.New("Parameters storage corrupt: Storing invalid AdminPermissions.")
	}
	if (parametersNetworkType != networkType){
		return false, false, errors.New("getStoredParameters returning parameters of a different networkType")
	}
	if (currentParametersType != "AdminPermissions"){
		return false, false, errors.New("getStoredParameters returning parameters of a different parametersType")
	}
	if (len(listOfAdmins) != 1){
		return false, false, errors.New("Parameters storage is corrupt: storing AdminPermissions with more than 1 signer.")
	}

	masterAdminPublicKeyBytes, err := encoding.DecodeHexStringToBytes(masterAdminPublicKey)
	if (err != nil){
		return false, false, errors.New("Master admin public key is malformed: " + masterAdminPublicKey + ". Reason: " + err.Error())
	}

	areEqual := bytes.Equal(listOfAdmins[0][:], masterAdminPublicKeyBytes)
	if (areEqual == false){
		return false, false, errors.New("Parameters storage is corrupt: storing AdminPermissions with different admin key.")
	}

	authorizedAdminsListString, exists := adminPermissionsMap[parametersType + "_AuthorizedAdmins"]
	if (exists == false) {
		return false, false, errors.New("Stored AdminPermissions parameters missing " + parametersType + "_AuthorizedAdmins")
	}

	authorizedAdminsList := strings.Split(authorizedAdminsListString, "+")
	if (len(authorizedAdminsList) == 0){
		return false, false, errors.New("Stored AdminPermissions parameters contains empty authorizedAdmins list")
	}

	authorizedAdminKeysList := make([][32]byte, 0, len(authorizedAdminsList))

	for _, adminPublicKeyHex := range authorizedAdminsList{

		adminPublicKey, err := encoding.DecodeHexStringToBytes(adminPublicKeyHex)
		if (err != nil){
			return false, false, errors.New("Stored AdminPermissions parameters contains empty authorizedAdmins list: Key not hex.")
		}

		if (len(adminPublicKey) != 32){
			return false, false, errors.New("Stored AdminPermissions parameters contains empty authorizedAdmins list: Key is invalid length.")
		}

		adminPublicKeyArray := [32]byte(adminPublicKey)

		authorizedAdminKeysList = append(authorizedAdminKeysList, adminPublicKeyArray)
	}

	minimumSigners, exists := adminPermissionsMap[parametersType + "_MinimumSigners"]
	if (exists == false) {
		return false, false, errors.New("Parameters storage is corrupt: AdminPermissions missing MinimumSigners but contains AuthorizedAdmins")
	}

	minimumNumberOfSigners, err := helpers.ConvertStringToInt(minimumSigners)
	if (err != nil) {
		return false, false, errors.New("Parameters storage is corrupt: AdminPermissions contains invalid minimumSigners: " + minimumSigners)
	}

	if (len(authorizedAdminKeysList) < minimumNumberOfSigners){
		return false, false, errors.New("Parameters storage is corrupt: AdminPermissions contains less authorizedAdmins than minimumSigners")
	}

	minimumCreationTime, exists := adminPermissionsMap[parametersType + "_MinimumCreationTime"]
	if (exists == false) {
		return false, false, errors.New("Parameters storage is corrupt: AdminPermissions missing minimumCreationTime but contains AuthorizedAdmins for parameterType.")
	}

	minimumCreationTimeInt64, err := helpers.ConvertStringToInt64(minimumCreationTime)
	if (err != nil) {
		return false, false, errors.New("Parameters storage is corrupt: AdminPermissions contains invalid minimumCreationTime: " + minimumCreationTime)
	}

	if (parametersCreationTime < minimumCreationTimeInt64){
		return false, false, nil
	}

	verifiedAdminsCount := 0

	for _, adminPublicKey := range parametersSigners{

		isInList := slices.Contains(authorizedAdminKeysList, adminPublicKey)
		if (isInList == true){
			verifiedAdminsCount += 1
		}
	}

	if (verifiedAdminsCount < minimumNumberOfSigners){
		// This parameters file does not have enough authorized signers
		// Either the provider of the parameters file is malicious, or the permissions file has updated 
		//		and the provider/we have not received the newest file
		return true, false, nil
	}

	return true, true, nil
}

//Outputs:
//	-bool: Parameters exist
//	-[]byte: Parameters bytes
//	-error
func getStoredParameters(parametersType string, networkType byte)(bool, []byte, error){

	seekiaDirectory, err := localFilesystem.GetSeekiaDataFolderPath()
	if (err != nil) { return false, nil, err }

	networkTypeString := helpers.ConvertByteToString(networkType)

	networkTypeFoldername := "Network" + networkTypeString

	parametersFilename := parametersType + ".messagepack"
	parametersFilepath := goFilepath.Join(seekiaDirectory, "Parameters", networkTypeFoldername, parametersFilename)

	exists, fileBytes, err := localFilesystem.GetFileContents(parametersFilepath)
	if (err != nil) { return false, nil, err }
	if (exists == false){
		return false, nil, nil
	}

	return true, fileBytes, nil
}



