
// readProfiles provides functions to read and validate user profiles

package readProfiles


import "seekia/internal/cryptography/blake3"
import "seekia/internal/cryptography/edwardsKeys"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/profiles/profileFormat"

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

import "strings"
import "encoding/binary"
import "errors"
import "slices"

// Verifies a profile
//Outputs:
//	-bool: Is valid
//	-error: Will return error if there is a bug in the function
func VerifyProfile(inputProfile []byte)(bool, error){

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

	return true, nil
}

func VerifyProfileHash(inputHash [28]byte, profileTypeProvided bool, expectedProfileType string, isDisabledProvided bool, expectedIsDisabled bool)(bool, error){

	if (profileTypeProvided == true){
		if (expectedProfileType != "Mate" && expectedProfileType != "Host" && expectedProfileType != "Moderator"){
			return false, errors.New("VerifyProfileHash called with invalid provided expectedProfileType: " + expectedProfileType)
		}
	}

	profileType, profileIsDisabled, err := ReadProfileHashMetadata(inputHash)
	if (err != nil){
		return false, nil
	}
	if (profileTypeProvided == true){
		if (profileType != expectedProfileType){
			return false, nil
		}
	}
	if (isDisabledProvided == true){
		if (profileIsDisabled != expectedIsDisabled){
			return false, nil
		}
	}

	return true, nil
}


func VerifyAttributeHash(inputHash [27]byte, profileTypeProvided bool, expectedProfileType string, isCanonicalProvided bool, expectedIsCanonical bool)(bool, error){

	if (profileTypeProvided == true){
		if (expectedProfileType != "Mate" && expectedProfileType != "Host" && expectedProfileType != "Moderator"){
			return false, errors.New("VerifyAttributeHash called with invalid provided expectedProfileType: " + expectedProfileType)
		}
	}

	profileType, attributeIsCanonical, err := ReadAttributeHashMetadata(inputHash)
	if (err != nil){
		return false, nil
	}
	if (profileTypeProvided == true){
		if (profileType != expectedProfileType){
			return false, nil
		}
	}
	if (isCanonicalProvided == true){
		if (attributeIsCanonical != expectedIsCanonical){
			return false, nil
		}
	}

	return true, nil
}

//Outputs:
//	-string: Profile Type
//	-bool: Profile is disabled
//	-error:
func ReadProfileHashMetadata(profileHash [28]byte)(string, bool, error){

	metadataByte := profileHash[27]

	switch metadataByte{

		case 1:{
			return "Mate", true, nil
		}
		case 2:{
			return "Mate", false, nil
		}
		case 3:{
			return "Host", true, nil
		}
		case 4:{
			return "Host", false, nil
		}
		case 5:{
			return "Moderator", true, nil
		}
		case 6:{
			return "Moderator", false, nil
		}
	}

	profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])

	return "", false, errors.New("ReadProfileHashMetadata called with invalid profileHash: " + profileHashHex)
}

//Outputs:
//	-string: Identity Type of author
//	-bool: Attribute is canonical
//	-error:
func ReadAttributeHashMetadata(attributeHash [27]byte)(string, bool, error){

	metadataByte := attributeHash[26]

	switch metadataByte{

		case 1:{
			return "Mate", true, nil
		}
		case 2:{
			return "Mate", false, nil
		}
		case 3:{
			return "Host", true, nil
		}
		case 4:{
			return "Host", false, nil
		}
		case 5:{
			return "Moderator", true, nil
		}
		case 6:{
			return "Moderator", false, nil
		}
	}

	attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])

	return "", false, errors.New("ReadAttributeHashMetadata called with invalid attributeHash: " + attributeHashHex)
}

// This function will read a profile and compute its hash.
//Outputs:
//	-bool: Able to read profile
//	-[28]byte: Profile hash
//	-int: Profile version
//	-byte: Network type (1 == Mainnet, 2 == Testnet1)
//	-[16]byte: Profile author identity hash
//	-int64: Profile creation time (alleged, can be faked)
//	-bool: Profile is disabled
//	-map[int]messagepack.RawMessage: Raw profile map (Attribute Identifier -> Attribute messagepack bytes value)
//	-error (will return err if there is a bug)
func ReadProfileAndHash(verifyProfile bool, inputProfile []byte)(bool, [28]byte, int, byte, [16]byte, int64, bool, map[int]messagepack.RawMessage, error){

	ableToRead, profileVersion, networkType, profileAuthor, profileCreationTime, profileIsDisabled, rawProfileMap, err := ReadProfile(verifyProfile, inputProfile)
	if (err != nil) { return false, [28]byte{}, 0, 0, [16]byte{}, 0, false, nil, err }
	if (ableToRead == false){
		return false, [28]byte{}, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	profileHashWithoutMetadataByte, err := blake3.GetBlake3HashAsBytes(27, inputProfile)
	if (err != nil) { return false, [28]byte{}, 0, 0, [16]byte{}, 0, false, nil, err }

	profileAuthorIdentityType, err := identity.GetIdentityTypeFromIdentityHash(profileAuthor)
	if (err != nil) { return false, [28]byte{}, 0, 0, [16]byte{}, 0, false, nil, err }

	getProfileHashMetadataByte := func()byte{

		if (profileAuthorIdentityType == "Mate"){

			if (profileIsDisabled == true){
				return 1
			}

			return 2

		} else if (profileAuthorIdentityType == "Host"){

			if (profileIsDisabled == true){
				return 3
			}

			return 4
		}
		// profileAuthorIdentityType == "Moderator"

		if (profileIsDisabled == true){
			return 5
		}

		return 6
	}

	profileHashMetadataByte := getProfileHashMetadataByte()

	profileHashSlice := append(profileHashWithoutMetadataByte, profileHashMetadataByte)

	profileHash := [28]byte(profileHashSlice)

	return true, profileHash, profileVersion, networkType, profileAuthor, profileCreationTime, profileIsDisabled, rawProfileMap, nil
}


// This function reads a profile without computing the profile's hash
// This is faster because the profile's bytes do not need to be hashed
//Outputs:
//	-bool: Able to read profile
//	-int: Profile version
//	-byte: Network type (1 == Mainnet, 2 == Testnet1)
//	-[16]byte: Profile author identity hash
//	-int64: Profile creation time (alleged, can be faked)
//	-bool: Profile is disabled
//	-map[int]messagepack.RawMessage: Raw profile map (Attribute identifier -> Attribute raw bytes)
//	-error (will return err if there is a bug)
func ReadProfile(verifyProfile bool, inputProfile []byte)(bool, int, byte, [16]byte, int64, bool, map[int]messagepack.RawMessage, error){

	var profileSlice []messagepack.RawMessage

	err := messagepack.Unmarshal(inputProfile, &profileSlice)
	if (err != nil){
		// Profile is malformed: Invalid profile messagepack
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	if (len(profileSlice) != 2){
		// Profile is malformed: Invalid profile messagepack
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	profileSignatureEncoded := profileSlice[0]
	profileContentEncoded := profileSlice[1]

	profileSignature, err := encoding.DecodeRawMessagePackTo64ByteArray(profileSignatureEncoded)
	if (err != nil){
		// Profile is malformed: Invalid profile signature
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	// This map will contain the profile content
	// The fields are encoded as identifiers, which need to be converted to names
	// Identifiers are integers, which we use instead of encoding the full name of the attribute
	// We do this to make profiles smaller
	// For example, 3 == "ProfileType"
	rawProfileMap := make(map[int]messagepack.RawMessage)

	err = encoding.DecodeMessagePackBytes(false, profileContentEncoded, &rawProfileMap)
	if (err != nil) {
		// Profile is malformed: Profile content map is invalid
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	profileVersionEncoded, exists := rawProfileMap[1]
	if (exists == false) {
		// Profile is malformed: Missing profileVersion
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	profileVersion, err := encoding.DecodeRawMessagePackToInt(profileVersionEncoded)
	if (err != nil){
		// Profile is malformed: Invalid profile version
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	if (profileVersion != 1){
		// We cannot read this profile. It was created by an newer version of Seekia.
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	networkTypeEncoded, exists := rawProfileMap[51]
	if (exists == false) {
		// Profile is malformed: Missing NetworkType
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	networkType, err := encoding.DecodeRawMessagePackToByte(networkTypeEncoded)
	if (err != nil){
		// Profile is malformed: Invalid network type
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		// Profile is malformed: Invalid network type
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	identityKeyEncoded, exists := rawProfileMap[2]
	if (exists == false) {
		// Profile is malformed: Missing identity key.
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	identityKey, err := encoding.DecodeRawMessagePackTo32ByteArray(identityKeyEncoded)
	if (err != nil){
		// Profile is malformed: Invalid identity key
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	profileTypeEncoded, exists := rawProfileMap[3]
	if (exists == false) {
		// Profile is malformed: missing ProfileType.
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	profileType, err := encoding.DecodeRawMessagePackToString(profileTypeEncoded)
	if (err != nil){
		// Profile is malformed: Invalid ProfileType.
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
		// Profile is malformed: Invalid ProfileType.
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	identityHash, err := identity.ConvertIdentityKeyToIdentityHash(identityKey, profileType)
	if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }

	creationTimeEncoded, exists := rawProfileMap[4]
	if (exists == false) {
		// Profile is malformed: missing CreationTime
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	creationTimeInt64, err := encoding.DecodeRawMessagePackToInt64(creationTimeEncoded)
	if (err != nil) {
		// Profile is malformed: Contains invalid CreationTime
		return false, 0, 0, [16]byte{}, 0, false, nil, nil
	}

	if (verifyProfile == true){

		isValid := helpers.VerifyCreationTime(creationTimeInt64)
		if (isValid == false){
			// Profile is malformed: Contains invalid CreationTime
			return false, 0, 0, [16]byte{}, 0, false, nil, nil
		}
	}

	if (verifyProfile == true){

		contentHash, err := blake3.Get32ByteBlake3Hash(profileContentEncoded)
		if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }

		signatureIsValid := edwardsKeys.VerifySignature(identityKey, profileSignature, contentHash)
		if (signatureIsValid == false) {
			// Profile is malformed: Invalid signature.
			return false, 0, 0, [16]byte{}, 0, false, nil, nil
		}
	}

	isDisabledEncoded, exists := rawProfileMap[5]
	if (exists == true){

		// Profile is disabled.

		isDisabledBool, err := encoding.DecodeRawMessagePackToBool(isDisabledEncoded)
		if (err != nil){
			// Profile is malformed: Invalid IsDisabled attribute.
			return false, 0, 0, [16]byte{}, 0, false, nil, nil
		}

		if (isDisabledBool == false){
			// Profile is malformed: Invalid Disabled attribute.
			return false, 0, 0, [16]byte{}, 0, false, nil, nil
		}

		if (len(rawProfileMap) != 6){
			// Profile is malformed: Invalid Disabled profile.
			return false, 0, 0, [16]byte{}, 0, false, nil, nil
		}

		return true, profileVersion, networkType, identityHash, creationTimeInt64, true, rawProfileMap, nil
	}

	if (verifyProfile == true){

		profileAttributeObjectsList, err := profileFormat.GetProfileAttributeObjectsList()
		if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }

		for _, attributeObject := range profileAttributeObjectsList{

			attributeName := attributeObject.AttributeName

			if (attributeName == "Disabled" || attributeName == "ProfileType" || attributeName == "IdentityKey" || attributeName == "ProfileVersion" || attributeName == "NetworkType" || attributeName == "CreationTime"){
				// We already verified these attributes
				continue
			}

			attributeProfileVersions := attributeObject.ProfileVersions

			isRelevantVersion := slices.Contains(attributeProfileVersions, profileVersion)
			if (isRelevantVersion == false){
				// This attribute does not belong to this profile version
				// Profiles created in this version do not have this attribute
				continue
			}

			attributeProfileTypes, err := attributeObject.GetProfileTypes(profileVersion)
			if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }

			isRelevantProfileType := slices.Contains(attributeProfileTypes, profileType)
			if (isRelevantProfileType == false){
				continue
			}

			attributeIdentifier := attributeObject.AttributeIdentifier

			attributeIsRequired, err := attributeObject.GetIsRequired(profileVersion)
			if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }

			attributeValueBytes, exists := rawProfileMap[attributeIdentifier]
			if (exists == false){

				if (attributeIsRequired == true){
					// Profile is malformed: Profile is missing a required attribute
					return false, 0, 0, [16]byte{}, 0, false, nil, nil
				}

				continue
			}

			attributeIsValid, attributeValueString, err := formatProfileAttributeRawMessagePackToString(attributeName, attributeValueBytes)
			if (err != nil) { return false, 0, 0, [16]byte{}, 0, false, nil, err }
			if (attributeIsValid == false){
				// Profile is malformed: Profile contains an invalid attribute value
				return false, 0, 0, [16]byte{}, 0, false, nil, nil
			}

			attributeValueIsValid, _, err := attributeObject.CheckValueFunction(profileVersion, profileType, attributeValueString)
			if (err != nil){ return false, 0, 0, [16]byte{}, 0, false, nil, err }
			if (attributeValueIsValid == false){
				// Profile is malformed: Profile contains an invalid attribute value
				return false, 0, 0, [16]byte{}, 0, false, nil, nil
			}

			if (attributeIsRequired == false){

				// We make sure profile has all the mandatory attributes for this attribute
				// We don't need to perform this check if the attribute is required

				mandatoryAttributesList, err := attributeObject.GetMandatoryAttributes(profileVersion)
				if (err != nil){ return false, 0, 0, [16]byte{}, 0, false, nil, err }

				for _, mandatoryAttributeName := range mandatoryAttributesList{

					mandatoryAttributeIdentifier, err := profileFormat.GetAttributeIdentifierFromAttributeName(mandatoryAttributeName)
					if (err != nil){
						return false, 0, 0, [16]byte{}, 0, false, nil, errors.New("GetMandatoryAttributes returning unknown attribute name: " + mandatoryAttributeName)
					}

					_, exists := rawProfileMap[mandatoryAttributeIdentifier]
					if (exists == false){
						// Profile is malformed: Missing a mandatory attribute.
						return false, 0, 0, [16]byte{}, 0, false, nil, nil
					}
				}
			}
		}
	}

	return true, profileVersion, networkType, identityHash, creationTimeInt64, false, rawProfileMap, nil
}

// This function must be called on profiles which have already been verified
// Outputs:
//	-bool: Attribute exists
//	-string: Attribute Value (Formatted)
//	-error
func GetFormattedProfileAttributeFromRawProfileMap(rawProfileMap map[int]messagepack.RawMessage, attributeName string)(bool, string, error){

	attributeIdentifier, err := profileFormat.GetAttributeIdentifierFromAttributeName(attributeName)
	if (err != nil) {
		return false, "", errors.New("GetFormattedProfileAttributeFromRawProfileMap failed: " + err.Error())
	}

	attributeValueBytes, exists := rawProfileMap[attributeIdentifier]
	if (exists == false){
		return false, "", nil
	}

	attributeIsValid, attributeFormatted, err := formatProfileAttributeRawMessagePackToString(attributeName, attributeValueBytes)
	if (err != nil){ return false, "", err }
	if (attributeIsValid == false){
		// Profile is malformed.
		// This should never happen, because this function should only be called after the profile has been verified by ReadProfile

		return false, "", errors.New("GetFormattedProfileAttributeFromRawProfileMap called with raw profile map containing invalid " + attributeName + " attribute value.")
	}

	return true, attributeFormatted, nil
}


// We use this function to convert attributes from raw MessagePack to string
//Outputs:
//	-bool: Attribute is valid
//		-Be aware that this function does not fully verify attributes
//	-string: Attribute formatted
//	-error
func formatProfileAttributeRawMessagePackToString(attributeName string, attributeValueBytes messagepack.RawMessage)(bool, string, error){

	switch attributeName{

		// Some attributes are encoded in special ways in MessagePack to save space
		// For example, CreationTime is encoded as a int64 rather than a string

		case "ProfileVersion":{
	
			profileVersion, err := encoding.DecodeRawMessagePackToInt(attributeValueBytes)
			if (err != nil){
				// Profile is malformed: Invalid profile version
				return false, "", nil
			}

			if (profileVersion != 1){
				// We cannot read this profile. It was created by an newer version of Seekia.
				// This should never happen, because this function should only be called after the profile's network type has been verified
				return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with unknown ProfileVersion.")
			}

			return true, "1", nil
		}
		case "NetworkType":{

			networkType, err := encoding.DecodeRawMessagePackToByte(attributeValueBytes)
			if (err != nil){
				// Profile is malformed: Invalid network type
				// This should never happen, because this function should only be called after the profile's networkType has been verified
				return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid NetworkType.")
			}

			isValid := helpers.VerifyNetworkType(networkType)
			if (isValid == false){
				// Profile malformed: Invalid network type
				// This should never happen, because this function should only be called after the profile's networkType has been verified
				return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid NetworkType.")
			}

			networkTypeString := helpers.ConvertByteToString(networkType)

			return true, networkTypeString, nil
		}
		case "IdentityKey":{

			identityKey, err := encoding.DecodeRawMessagePackTo32ByteArray(attributeValueBytes)
			if (err != nil){
				// Profile is malformed: Invalid identity key
				// This should never happen, because this function should only be called after the profile's IdentityKey has been verified
				return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid IdentityKey.")
			}

			identityKeyHexString := encoding.EncodeBytesToHexString(identityKey[:])

			return true, identityKeyHexString, nil
		}
		case "CreationTime":{

			creationTimeInt64, err := encoding.DecodeRawMessagePackToInt64(attributeValueBytes)
			if (err != nil) {
				// Profile is malformed: Contains invalid CreationTime
				// This should never happen, because this function should only be called after the profile's CreationTime has been verified
				return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid CreationTime.")
			}

			creationTimeString := helpers.ConvertInt64ToString(creationTimeInt64)

			return true, creationTimeString, nil
		}
		case "Disabled":{

			isDisabledBool, err := encoding.DecodeRawMessagePackToBool(attributeValueBytes)
			if (err != nil){
				// Profile is malformed: Contains invalid Disabled attribute
				// This should never happen, because this function should only be called after the profile's Disabled has been verified
				return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid Disabled.")
			}

			if (isDisabledBool == false){
				// Profile is malformed: Contains invalid Disabled attribute
				// This should never happen, because this function should only be called after the profile's Disabled has been verified
				return false, "", errors.New("formatProfileAttributeRawMessagePackToString called with invalid Disabled.")
			}

			return true, "Yes", nil
		}
		case "NaclKey", "MlkemKey":{

			keyBytes, err := encoding.DecodeRawMessagePackToBytes(attributeValueBytes)
			if (err != nil) {
				// Profile is malformed: Contains invalid NaclKey/MlkemKey attribute
				return false, "", nil
			}

			keyString := encoding.EncodeBytesToBase64String(keyBytes)

			return true, keyString, nil
		}
		case "Photos":{

			var rawPhotoBytesList [][]byte

			err := encoding.DecodeMessagePackBytes(false, attributeValueBytes, &rawPhotoBytesList) 
			if (err != nil) {
				// Profile is malformed: Contains invalid Photos attribute
				return false, "", nil
			}

			if (len(rawPhotoBytesList) == 0){
				// Profile is malformed: Contains invalid Photos attribute
				return false, "", nil
			}

			// We use this to build the output
			// The output is a "+" delimited string of each photo's bytes encoded in Base64
			var attributeBuilder strings.Builder

			finalIndex := len(rawPhotoBytesList) - 1

			for index, photoBytes := range rawPhotoBytesList{

				photoBase64 := encoding.EncodeBytesToBase64String(photoBytes)

				attributeBuilder.WriteString(photoBase64)

				if (index != finalIndex){
					attributeBuilder.WriteString("+")
				}
			}

			photoAttributeString := attributeBuilder.String()

			return true, photoAttributeString, nil
		}
	}

	// Attribute is not encoded in a special way.
	// It is encoded as a unicode string.

	attributeValueString, err := encoding.DecodeRawMessagePackToString(attributeValueBytes)
	if (err != nil) { return false, "", err }

	return true, attributeValueString, nil
}

// Attribute hashes are used for moderation
// A profile's attribute hash can be reviewed without reviewing the whole profile
// This function does not verify the raw profile map. The raw profile map must first be verified
// Outputs:
//	-map[int][27]byte: Profile attribute hashes map (Attribute identifier -> Attribute hash)
//	-bool: Profile is canonical (all attributes are canonical)
//	-error
func GetProfileAttributeHashesMap(profileAuthor [16]byte, profileVersion int, profileNetworkType byte, rawProfileMap map[int]messagepack.RawMessage)(map[int][27]byte, bool, error){

	profileType, err := identity.GetIdentityTypeFromIdentityHash(profileAuthor)
	if (err != nil){
		profileAuthorHex := encoding.EncodeBytesToHexString(profileAuthor[:])
		return nil, false, errors.New("GetProfileAttributeHashesMap called with invalid profileAuthor: " + profileAuthorHex)
	}

	_, exists := rawProfileMap[5]
	if (exists == true){

		// Profile is disabled.
		// A disabled profile has an empty attribute hashes map.
		// All disabled profile are also canonical

		emptyMap := make(map[int][27]byte)

		return emptyMap, true, nil
	}

	// We use a prefix when creating attribute hashes
	// The prefix starts with the string "profileattributehashsalt" decoded from base32 to bytes
	attributeHashInputPrefix := []byte{124, 92, 84, 44, 128, 156, 226, 128, 210, 100, 56, 36, 121, 1, 115}

	attributeHashInputPrefix = append(attributeHashInputPrefix, profileAuthor[:]...)
	attributeHashInputPrefix = append(attributeHashInputPrefix, profileNetworkType)


	// Map Structure: Attribute Identifier -> Attribute hash
	profileAttributeHashesMap := make(map[int][27]byte)

	// We set this variable to false if any attribute is not canonical
	profileIsCanonical := true

	for attributeIdentifier, attributeRawValueBytes := range rawProfileMap{

		attributeObject, err := profileFormat.GetAttributeObjectFromAttributeIdentifier(attributeIdentifier)
		if (err != nil) { return nil, false, err }

		attributeName := attributeObject.AttributeName

		if (attributeName == "ProfileType" || attributeName == "IdentityKey" || attributeName == "ProfileVersion" || attributeName == "NetworkType"){
			// We don't need to store these attributes in the attribute hashes map
			continue
		}

		//Outputs:
		//	-bool: Attribute is canonical
		//	-error
		getAttributeIsCanonical := func()(bool, error){

			getIsCanonicalFunction := attributeObject.GetIsCanonical

			attributeIsCanonicalInfo, err := getIsCanonicalFunction(profileVersion)
			if (err != nil) { return false, err }

			if (attributeIsCanonicalInfo == "Always"){
				return true, nil
			}
			if (attributeIsCanonicalInfo == "Never"){
				return false, nil
			}
			// attributeIsCanonicalInfo == "Sometimes"
			// We have to manually check to see if the attribute's value is canonical

			valueIsValid, attributeValueString, err := formatProfileAttributeRawMessagePackToString(attributeName, attributeRawValueBytes)
			if (err != nil) { return false, err }
			if (valueIsValid == false){
				// Profile contains an invalid attribute value
				return false, errors.New("GetProfileAttributeHashesMap called with profile containing invalid attribute value for attribute: " + attributeName)
			}

			valueIsValid, attributeIsCanonical, err := attributeObject.CheckValueFunction(profileVersion, profileType, attributeValueString)
			if (err != nil){ return false, err }
			if (valueIsValid == false){
				// Profile contains an invalid attribute value
				return false, errors.New("GetProfileAttributeHashesMap called with profile containing invalid attribute value for attribute: " + attributeName + ". Attribute value: " + attributeValueString)
			}

			return attributeIsCanonical, nil
		}

		attributeIsCanonical, err := getAttributeIsCanonical()
		if (err != nil) { return nil, false, err }
		if (attributeIsCanonical == false){
			profileIsCanonical = false
		}

		// We now create the attribute hash
		// Each attribute hash represents an attribute value created by a specific author
		//	
		// We use the messagePack encoded bytes to create the attribute hash
		// I'm not sure, but it may be possible to encode the same value using multiple different messagepack encodings
		// For example, a number could be encoded in two different ways using raw messagepack
		// This would result in two different attribute hashes for the same attribute value
		// This is fine
		// It would only be a problem if an attribute hash represented two different attribute values

		if (attributeIdentifier < 1 || attributeIdentifier > 4294967295){
			return nil, false, errors.New("profileFormat contains invalid attribute identifier.")
		}

		attributeIdentifierUint32 := uint32(attributeIdentifier)

		// We copy the prefix and append the attribute identifier and attribute raw value bytes
		// We then feed that into blake3

		hashInputBytes := slices.Clone(attributeHashInputPrefix)

		hashInputBytes = binary.LittleEndian.AppendUint32(hashInputBytes, attributeIdentifierUint32)

		hashInputBytes = append(hashInputBytes, attributeRawValueBytes...)

		attributeHashWithoutMetadata, err := blake3.GetBlake3HashAsBytes(26, hashInputBytes)
		if (err != nil) { return nil, false, err }

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

			if (profileType == "Mate"){
				if (attributeIsCanonical == true){
					return 1, nil
				}
				return 2, nil
			}
			if (profileType == "Host"){
				if (attributeIsCanonical == true){
					return 3, nil
				}
				return 4, nil
			}
			if (profileType == "Moderator"){
				if (attributeIsCanonical == true){
					return 5, nil	
				}
				return 6, nil
			}
			return 0, errors.New("GetIdentityTypeFromIdentityHash returning invalid profileType: " + profileType)
		}

		attributeHashMetadataByte, err := getMetadataByte()
		if (err != nil) { return nil, false, err }

		attributeHashBytes := append(attributeHashWithoutMetadata, attributeHashMetadataByte)

		attributeHash := [27]byte(attributeHashBytes)

		profileAttributeHashesMap[attributeIdentifier] = attributeHash
	}

	return profileAttributeHashesMap, profileIsCanonical, nil
}


