
// viewableProfiles provides functions to retrieve viewable user profiles

package viewableProfiles

// A viewable profile is one that can be shown to non-moderators during normal use of Seekia

// A viewable Mate profile is one that has been approved.
// A viewable Host/Moderator profile is one that has not been banned.

// A viewable profile must also be created by a non-banned identity
// All viewable statuses are derived from the profile/identity sticky consensus statuses, not their realtime consensus statuses

import "seekia/internal/badgerDatabase"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/moderation/verifiedStickyStatus"
import "seekia/internal/moderation/trustedViewableStatus"
import "seekia/internal/profiles/calculatedAttributes"
import "seekia/internal/profiles/readProfiles"

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

import "errors"

// This function gets any attribute from a user's newest viewable profile. It includes calculated attributes.
//Outputs:
//	-bool: Profile exists
//	-int: Profile version
//	-bool: Attribute exists
//	-string: Attribute value
//	-error
func GetAnyAttributeFromNewestViewableUserProfile(identityHash [16]byte, networkType byte, attribute string, allowTrustedStatus bool, allowUnknownStatus bool, alwaysAllowDisabled bool)(bool, int, bool, string, error){

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

	profileExists, _, retrieveAnyAttributeFunction, err := GetRetrieveAnyNewestViewableUserProfileAttributeFunction(identityHash, networkType, allowTrustedStatus, allowUnknownStatus, alwaysAllowDisabled)
	if (err != nil) { return false, 0, false, "", err }
	if (profileExists == false){
		return false, 0, false, "", nil
	}

	exists, profileVersion, retrievedAttribute, err := retrieveAnyAttributeFunction(attribute)
	if (err != nil) { return false, 0, false, "", err }
	if (exists == false){
		return true, 0, false, "", nil
	}
	return true, profileVersion, true, retrievedAttribute, nil
}

// This function returns a function to retrieve any attribute from a user's newest viewable profile.
// This makes retrieving multiple attributes faster.
// The newest viewable profile only needs to be found and read into memory once.
//Outputs:
//	-bool: Profile exists
//	-[28]byte: Profile hash
//	-func(string)(bool, int, string, error) - Retrieve any attribute function
//	-error
func GetRetrieveAnyNewestViewableUserProfileAttributeFunction(identityHash [16]byte, networkType byte, allowTrustedStatus bool, allowUnknownStatus bool, alwaysAllowDisabled bool)(bool, [28]byte, func(string)(bool, int, string, error), error){

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

	profileExists, profileVersion, profileHash, _, _, rawProfileMap, err := GetNewestViewableUserProfile(identityHash, networkType, allowTrustedStatus, allowUnknownStatus, alwaysAllowDisabled)
	if (err != nil) { return false, [28]byte{}, nil, err }
	if (profileExists == false){

		return false, [28]byte{}, nil, nil
	}

	getAnyAttributeFunction, err := calculatedAttributes.GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(profileVersion, rawProfileMap)
	if (err != nil){ return false, [28]byte{}, nil, err }

	return true, profileHash, getAnyAttributeFunction, nil
}

//Inputs:
//	-[16]byte: Identity hash of user whose profile we are retrieving
//	-byte: Network type to retrieve profile for
//	-bool: Allow trusted viewable status to be retrieved.
//		-If false, only verified viewable statuses will be allowed (this is used for hosts)
//	-bool: Allow Unknown Status
//		-This is only used to find hosts to query from. This must be false for hosts retrieving viewable host profiles to share.
//	-bool: Always allow disabled profiles
//		-If true, the function will return disabled profiles, even if the identity is banned.
//		-If false, a disabled profile will be considered unviewable if the identity is banned.
//Outputs:
//	-bool: Viewable Profile exists
//	-int: Profile version
//	-[28]byte: Viewable Profile hash
//	-[]byte: Viewable Profile bytes
//	-int64: Profile creation time
//	-map[int]messagepack.RawMessage: Viewable Profile Map
//	-error
func GetNewestViewableUserProfile(identityHash [16]byte, networkType byte, allowTrustedStatus bool, allowUnknownStatus bool, alwaysAllowDisabled bool)(bool, int, [28]byte, []byte, int64, map[int]messagepack.RawMessage, error){

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

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

	// We check if the identity is banned

	getIdentityIsViewableStatus := func()(bool, error){

		downloadingRequiredReviews, parametersExist, stickyConsensusEstablished, identityIsViewableStatus, err := verifiedStickyStatus.GetVerifiedIdentityIsViewableStickyStatus(identityHash, networkType)
		if (err != nil) { return false, err }
		if (downloadingRequiredReviews == true && parametersExist == true && stickyConsensusEstablished == true){

			return identityIsViewableStatus, nil
		} 
		if (allowTrustedStatus == false){

			// The trusted viewable status of the identity is unknown
			// We are not downloading the required reviews

			if (allowUnknownStatus == true){
				return true, nil
			}

			return false, nil
		}

		statusIsKnown, identityIsViewable, _, err := trustedViewableStatus.GetTrustedIdentityIsViewableStatus(identityHash, networkType)
		if (err != nil) { return false, err }
		if (statusIsKnown == true){
			return identityIsViewable, nil
		}

		if (allowUnknownStatus == true){
			return true, nil
		}
		return false, nil
	}
	
	identityIsViewableStatus, err := getIdentityIsViewableStatus()
	if (err != nil) { return false, 0, [28]byte{}, nil, 0, nil, err }

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

	newestViewableProfileFound := false
	newestViewableProfileVersion := 0
	var newestViewableProfileHash [28]byte
	newestViewableProfileIsDisabled := false
	newestViewableProfileRawProfileMap := make(map[int]messagepack.RawMessage)
	newestViewableProfileBytes := make([]byte, 0)
	newestViewableProfileCreationTime := int64(0)

	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 automatically in the background	
			continue
		}

		ableToRead, profileVersion, profileNetworkType, profileIdentityHash, profileCreationTime, profileIsDisabled, 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 != identityHash){
			return false, 0, [28]byte{}, nil, 0, nil, errors.New("Database corrupt: Identity Profiles list profile does not match identity hash.")
		}
		if (profileNetworkType != networkType){
			// The profile belongs to a different networkType
			// We skip it.
			continue
		}

		// Disabled profiles are always viewable, unless the author identity is banned.	

		if (profileIsDisabled == false){

			profileIsDisabled, profileMetadataIsKnown, profileNetworkType, profileIdentityHash, downloadingRequiredReviews, networkParametersExist, stickyConsensusEstablished, profileIsViewableStatus, err := verifiedStickyStatus.GetVerifiedProfileIsViewableStickyStatus(profileHash)
			if (err != nil) { return false, 0, [28]byte{}, nil, 0, nil, err }
			if (profileIsDisabled == true){
				return false, 0, [28]byte{}, nil, 0, nil, errors.New("GetVerifiedProfileIsViewableStickyStatus returning mismatched profileIsDisabled status")
			}
			if (profileMetadataIsKnown == false){
				// The metadata must have been deleted after we retrieved profiles from database. Skip profile
				continue
			}
			if (profileNetworkType != networkType){
				return false, 0, [28]byte{}, nil, 0, nil, errors.New("GetVerifiedProfileIsViewableStickyStatus returning mismatched profileNetworkType.")
			}
			if (profileIdentityHash != identityHash){
				return false, 0, [28]byte{}, nil, 0, nil, errors.New("GetVerifiedProfileIsViewableStickyStatus returning invalid profileIdentityHash")
			}

			// Outputs:
			//	-bool: Profile is viewable
			//	-error
			getProfileIsViewableStatus := func()(bool, error){

				if (downloadingRequiredReviews == true && networkParametersExist == true && stickyConsensusEstablished == true){

					return profileIsViewableStatus, nil
				}

				// Verified status is unknown. We must attempt to get trusted status

				if (allowTrustedStatus == false){

					// The trusted viewable status of the profile is unknown
					// We are not downloading the required reviews, or the profile is not downloaded

					if (allowUnknownStatus == true){
						return true, nil
					}
					// The profile cannot be verified as Viewable
					return false, nil
				}

				statusIsKnown, isViewable, _, err := trustedViewableStatus.GetTrustedProfileIsViewableStatus(profileHash)
				if (err != nil) { return false, err }
				if (statusIsKnown == true){

					return isViewable, nil
				}

				// We do not have a trusted or verified verdict. Profile's viewable status is unknown.

				if (allowUnknownStatus == true){
					return true, nil
				}

				return false, nil
			}

			profileIsViewable, err := getProfileIsViewableStatus()
			if (err != nil) { return false, 0, [28]byte{}, nil, 0, nil, err }
			if (profileIsViewable == false){
				// Profile is not viewable, so we skip it

				if (newestViewableProfileFound == true && newestViewableProfileIsDisabled == true && newestViewableProfileCreationTime < profileCreationTime){

					// This user created a non-disabled profile after creating their disabled profile
					// We reset the newestViewableProfileFound status so that we do not return the disabled profile
					// We only allow the disabled profile to be returned if it is the newest profile the user has created
					// Otherwise, users could see a user as being disabled, when in reality their newest profile has not been approved yet

					newestViewableProfileFound = false
				}

				continue
			}
		}

		if (newestViewableProfileFound == false || newestViewableProfileCreationTime < profileCreationTime){
			newestViewableProfileFound = true
			newestViewableProfileVersion = profileVersion
			newestViewableProfileHash = profileHash
			newestViewableProfileIsDisabled = profileIsDisabled
			newestViewableProfileBytes = profileBytes
			newestViewableProfileRawProfileMap = rawProfileMap
			newestViewableProfileCreationTime = profileCreationTime
		}
	}

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

	if (newestViewableProfileIsDisabled == true && alwaysAllowDisabled == true){
		return true, newestViewableProfileVersion, newestViewableProfileHash, newestViewableProfileBytes, newestViewableProfileCreationTime, newestViewableProfileRawProfileMap, nil
	}

	if (identityIsViewableStatus == false){
		return false, 0, [28]byte{}, nil, 0, nil, nil
	}
	
	return true, newestViewableProfileVersion, newestViewableProfileHash, newestViewableProfileBytes, newestViewableProfileCreationTime, newestViewableProfileRawProfileMap, nil
}


