
// profileStorage provides functions to store and retrieve downloaded profiles

package profileStorage

//TODO: Add a job to prune database entries for profiles/attributes which have been deleted

import "seekia/internal/badgerDatabase"
import "seekia/internal/contentMetadata"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/mySettings"
import "seekia/internal/profiles/readProfiles"

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

import "errors"
import "slices"

func GetNumberOfStoredProfiles()(int64, error){
	
	numberOfProfiles, err := badgerDatabase.GetNumberOfUserProfiles()
	if (err != nil) { return 0, err }

	return numberOfProfiles, nil
}

// Returns all identity hashes of all authors of profiles in the database
func GetAllStoredProfileIdentityHashes()([][16]byte, error){

	mateProfileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Mate")
	if (err != nil) { return nil, err }

	hostProfileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Host")
	if (err != nil) { return nil, err }

	moderatorProfileIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Moderator")
	if (err != nil) { return nil, err }

	allProfileIdentityHashesList := slices.Concat(mateProfileIdentityHashesList, hostProfileIdentityHashesList, moderatorProfileIdentityHashesList)

	return allProfileIdentityHashesList, nil
}

func GetAllStoredProfileHashes()([][28]byte, error){

	allMateProfileHashesList, err := badgerDatabase.GetAllProfileHashes("Mate")
	if (err != nil) { return nil, err }

	allHostProfileHashesList, err := badgerDatabase.GetAllProfileHashes("Host")
	if (err != nil) { return nil, err }

	allModeratorProfileHashesList, err := badgerDatabase.GetAllProfileHashes("Moderator")
	if (err != nil) { return nil, err }

	allProfileHashesList := slices.Concat(allMateProfileHashesList, allHostProfileHashesList, allModeratorProfileHashesList)

	return allProfileHashesList, nil
}


//Outputs:
//	-bool: Profile exists
//	-[]byte: Profile Bytes
//	-error
func GetStoredProfile(profileHash [28]byte)(bool, []byte, error){

	profileType, _, err := readProfiles.ReadProfileHashMetadata(profileHash)
	if (err != nil) {
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return false, nil, errors.New("GetStoredProfile called with invalid profileHash: " + profileHashHex)
	}

	exists, profileBytes, err := badgerDatabase.GetUserProfile(profileType, profileHash)
	if (err != nil) { return false, nil, err }
	if (exists == false){
		return false, nil, nil
	}

	return true, profileBytes, nil
}


// This function verifies the profile file is valid and adds the profile to the database
//Outputs:
//	-bool: Profile is well formed
//	-[28]byte: Profile hash of added profile
//	-error
func AddUserProfile(inputProfile []byte)(bool, [28]byte, error){

	ableToRead, newProfileHash, newProfileVersion, newProfileNetworkType, userIdentityHash, _, _, rawProfileMap, err := readProfiles.ReadProfileAndHash(true, inputProfile)
	if (err != nil) { return false, [28]byte{}, err }
	if (ableToRead == false){
		// Profile is invalid, host that we downloaded from must be malicious, or profile version is newer than our own.
		return false, [28]byte{}, nil
	}

	userIdentityType, err := identity.GetIdentityTypeFromIdentityHash(userIdentityHash)
	if (err != nil) {
		identityHashHex := encoding.EncodeBytesToHexString(userIdentityHash[:])
		return false, [28]byte{}, errors.New("IdentityHash invalid after profile has been verified: " + identityHashHex)
	}

	exists, _, err := badgerDatabase.GetUserProfile(userIdentityType, newProfileHash)
	if (err != nil) { return false, [28]byte{}, err }
	if (exists == true){
		// Profile already imported, skip it.
		return true, newProfileHash, nil
	}

	err = badgerDatabase.AddUserProfile(userIdentityType, newProfileHash, inputProfile)
	if (err != nil) { return false, [28]byte{}, err }

	err = badgerDatabase.AddIdentityProfileHash(userIdentityHash, newProfileHash)
	if (err != nil) { return false, [28]byte{}, err }

	profileAttributeHashesMap, _, err := readProfiles.GetProfileAttributeHashesMap(userIdentityHash, newProfileVersion, newProfileNetworkType, rawProfileMap)
	if (err != nil) { return false, [28]byte{}, err }

	for _, attributeHash := range profileAttributeHashesMap{

		err := badgerDatabase.AddAttributeProfile(attributeHash, newProfileHash)
		if (err != nil) { return false, [28]byte{}, err }
	}

	if (userIdentityType == "Mate"){

		err = mySettings.SetSetting("MyMatchesNeedRefreshYesNo", "Yes")
		if (err != nil) { return false, [28]byte{}, err }

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

		err = mySettings.SetSetting("ViewedHostsNeedsRefreshYesNo", "Yes")
		if (err != nil) { return false, [28]byte{}, err }

	} else if (userIdentityType == "Moderator"){

		err = mySettings.SetSetting("ViewedModeratorsNeedsRefreshYesNo", "Yes")
		if (err != nil) { return false, [28]byte{}, err }
	}

	err = mySettings.SetSetting("ViewedContentNeedsRefreshYesNo", "Yes")
	if (err != nil) { return false, [28]byte{}, err }

	return true, newProfileHash, nil
}

// This function will return a user's newest profile. Viewable status (approved/banned status) is ignored
//Outputs:
//	-bool: Profile exists
//	-int: Profile version
//	-[28]byte: Profile hash
//	-[]byte: Profile bytes
//	-int64: Profile creation time
//	-map[int]messagepack.RawMessage: Raw profile map
//	-error
func GetNewestUserProfile(userIdentityHash [16]byte, networkType byte)(bool, int, [28]byte, []byte, int64, map[int]messagepack.RawMessage, error){

	identityType, err := identity.GetIdentityTypeFromIdentityHash(userIdentityHash)
	if (err != nil) {
		userIdentityHashHex := encoding.EncodeBytesToHexString(userIdentityHash[:])
		return false, 0, [28]byte{}, nil, 0, nil, errors.New("GetNewestUserProfile called with invalid userIdentityHash: " + userIdentityHashHex)
	}

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

	exists, profileHashesList, err := badgerDatabase.GetIdentityProfileHashesList(userIdentityHash)
	if (err != nil) { return false, 0, [28]byte{}, nil, 0, nil, err }
	if (exists == false){
		return false, 0, [28]byte{}, nil, 0, nil, nil
	}

	anyProfileFound := false

	//TODO: Deal with 2 profiles with identical creationTimes
	// We must compare the profile hashes

	var newestProfileHash [28]byte
	newestProfileVersion := 0
	newestProfileCreationTime := int64(0)
	newestProfileBytes := make([]byte, 0)
	newestProfileRawProfileMap := make(map[int]messagepack.RawMessage)

	for _, profileHash := range profileHashesList{

		exists, profileBytes, err := badgerDatabase.GetUserProfile(identityType, profileHash)
		if (err != nil) { return false, 0, [28]byte{}, nil, 0, nil, err }
		if (exists == false){
			// Identity profile hashes list is outdated, it will be updated by databaseJobs			
			continue
		}

		ableToRead, profileVersion, profileNetworkType, profileIdentityHash, profileCreationTime, _, rawProfileMap, err := readProfiles.ReadProfile(false, profileBytes)
		if (err != nil) { return false, 0, [28]byte{}, nil, 0, nil, err }
		if (ableToRead == false){
			return false, 0, [28]byte{}, nil, 0, nil, errors.New("Database corrupt: Contains invalid profile.")
		}
		if (profileIdentityHash != userIdentityHash){
			return false, 0, [28]byte{}, nil, 0, nil, errors.New("Database corrupt: identity profiles list contains profile with different identityHash")
		}
		if (profileNetworkType != networkType){
			// This profile belongs to a different network type
			// Example of network types: Mainnet, Testnet1
			continue
		}

		if (anyProfileFound == false || profileCreationTime > newestProfileCreationTime){
			newestProfileVersion = profileVersion
			newestProfileHash = profileHash
			newestProfileCreationTime = profileCreationTime
			newestProfileRawProfileMap = rawProfileMap
			newestProfileBytes = profileBytes
		}

		anyProfileFound = true
	}

	if (anyProfileFound == false){
		return false, 0, [28]byte{}, nil, 0, nil, nil 
	}

	return true, newestProfileVersion, newestProfileHash, newestProfileBytes, newestProfileCreationTime, newestProfileRawProfileMap, nil
}




// This function is used to find metadata about a profile attribute
// We search through all stored profiles to find a profile that the attribute belongs to
// We may be able to verify the profile author while we are missing the actual profile content
// That would happen if we have saved the profile metadata and deleted the profile
//
//Outputs:
//	-bool: Metadata exists
//	-int: Attribute identifier
//	-[16]byte: Attribute author identity hash
//	-byte: Attribute network type
//	-[][28]byte: List of profile hashes with this attribute (they may be deleted/expired)
//	-error
func GetProfileAttributeMetadata(inputAttributeHash [27]byte)(bool, int, [16]byte, byte, [][28]byte, error){

	isValid, err := readProfiles.VerifyAttributeHash(inputAttributeHash, false, "", false, false)
	if (err != nil) { return false, 0, [16]byte{}, 0, nil, err }
	if (isValid == false){
		inputAttributeHashHex := encoding.EncodeBytesToHexString(inputAttributeHash[:])
		return false, 0, [16]byte{}, 0, nil, errors.New("GetProfileAttributeMetadata called with invalid attributeHash: " + inputAttributeHashHex)
	}

	anyExist, attributeProfileHashesList, err := badgerDatabase.GetAttributeProfilesList(inputAttributeHash)
	if (err != nil) { return false, 0, [16]byte{}, 0, nil, err }
	if (anyExist == false){
		return false, 0, [16]byte{}, 0, nil, nil
	}

	for _, profileHash := range attributeProfileHashesList{

		profileMetadataExists, _, profileNetworkType, profileAuthor, _, profileIsDisabled, _, profileAttributeHashesMap, err := contentMetadata.GetProfileMetadata(profileHash)
		if (err != nil) { return false, 0, [16]byte{}, 0, nil, err }
		if (profileMetadataExists == false){
			continue
		}
		if (profileIsDisabled == true){
			// The profile is disabled, the attribute hash cannot belong to it.
			return false, 0, [16]byte{}, 0, nil, errors.New("AttributeProfilesList contains disabled profile.")
		}

		for attributeIdentifier, profileAttributeHash := range profileAttributeHashesMap{

			if (profileAttributeHash == inputAttributeHash){
				return true, attributeIdentifier, profileAuthor, profileNetworkType, attributeProfileHashesList, nil
			}
		}

		// This is only reached if the profile did not contain the attribute
		return false, 0, [16]byte{}, 0, nil, errors.New("AttributeProfilesList contains profile which does not contain the attribute.")
	}

	// We could not find the attribute's metadata

	return false, 0, [16]byte{}, 0, attributeProfileHashesList, nil
}


// This function is the same as GetProfileAttributeMetadata, except it attempts to find a full profile which contains the attribute
//Outputs:
//	-bool: Metadata exists
//	-int: Attribute identifier
//	-[16]byte: Attribute author identity hash
//	-byte: Attribute network type
//	-[][28]byte: List of profile hashes with this attribute (they may be deleted/expired)
//	-bool: Full Profile exists
//	-[28]byte: Full profile hash
//	-[]byte: Full Profile bytes
//	-error
func GetProfileAttributeMetadataAndProfile(inputAttributeHash [27]byte)(bool, int, [16]byte, byte, [][28]byte, bool, [28]byte, []byte, error){

	isValid, err := readProfiles.VerifyAttributeHash(inputAttributeHash, false, "", false, false)
	if (err != nil) { return false, 0, [16]byte{}, 0, nil, false, [28]byte{}, nil, err }
	if (isValid == false){
		inputAttributeHashHex := encoding.EncodeBytesToHexString(inputAttributeHash[:])
		return false, 0, [16]byte{}, 0, nil, false, [28]byte{}, nil, errors.New("GetProfileAttributeMetadataAndProfile called with invalid attributeHash: " + inputAttributeHashHex)
	}

	metadataExists, attributeIdentifier, attributeAuthor, attributeNetworkType, attributeProfileHashesList, err := GetProfileAttributeMetadata(inputAttributeHash)
	if (err != nil) { return false, 0, [16]byte{}, 0, nil, false, [28]byte{}, nil, err }
	if (metadataExists == false){

		// Attribute metadata does not exist.

		return false, 0, [16]byte{}, 0, nil, false, [28]byte{}, nil, nil
	}
	
	// Now we try to find a full profile which contains this attribute

	for _, profileHash := range attributeProfileHashesList{

		profileExists, profileBytes, err := GetStoredProfile(profileHash)
		if (err != nil) { return false, 0, [16]byte{}, 0, nil, false, [28]byte{}, nil, err }
		if (profileExists == true){
			return true, attributeIdentifier, attributeAuthor, attributeNetworkType, attributeProfileHashesList, true, profileHash, profileBytes, nil
		}
	}

	// We cannot find a full profile containing this attribute

	return true, attributeIdentifier, attributeAuthor, attributeNetworkType, attributeProfileHashesList, false, [28]byte{}, nil, nil
}

