
// reviewStorage provides functions to manage stored moderator reviews
// Reviews are created by moderators and determine the moderation verdict of messages, profiles, and identities.

package reviewStorage

import "seekia/internal/badgerDatabase"
import "seekia/internal/contentMetadata"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/messaging/readMessages"
import "seekia/internal/moderation/readReviews"
import "seekia/internal/mySettings"
import "seekia/internal/network/appNetworkType/getAppNetworkType"
import "seekia/internal/network/backgroundDownloads"
import "seekia/internal/profiles/readProfiles"

import "bytes"
import "errors"

func GetNumberOfStoredReviews()(int64, error){
	
	numberOfReviews, err := badgerDatabase.GetNumberOfReviews()
	if (err != nil) { return 0, err }

	return numberOfReviews, nil
}


//Outputs:
//	-bool: Review is well formed
//	-error
func AddReview(newReview []byte)(bool, error){

	ableToRead, reviewHash, _, _, reviewerIdentityHash, _, reviewType, reviewedHash, _, _, err := readReviews.ReadReviewAndHash(true, newReview)
	if (err != nil){ return false, err }
	if (ableToRead == false){
		// Review is malformed, do nothing
		// Host who sent review must be malicious.
		return false, nil
	}

	exists, _, err := badgerDatabase.GetReview(reviewHash)
	if (err != nil) { return false, err }
	if (exists == true){
		// Review already imported, nothing to do.		
		return true, nil
	}

	err = badgerDatabase.AddReview(reviewHash, newReview)
	if (err != nil) { return false, err }

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

	err = badgerDatabase.AddReviewerReviewHash(reviewerIdentityHash, reviewHash)
	if (err != nil) { return false, err }

	if (reviewType == "Identity"){

		if (len(reviewedHash) != 16){
			reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
			return false, errors.New("ReadReview returning invalid length reviewed Identity hash: " + reviewedHashHex)
		}

		reviewedIdentityHash := [16]byte(reviewedHash)

		err := badgerDatabase.AddIdentityReviewToList(reviewedIdentityHash, reviewHash)
		if (err != nil) { return false, err }

		return true, nil
	}
	if (reviewType == "Profile"){

		if (len(reviewedHash) != 28){
			reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
			return false, errors.New("ReadReview returning invalid length reviewed Profile hash: " + reviewedHashHex)
		}

		reviewedProfileHash := [28]byte(reviewedHash)

		err = badgerDatabase.AddProfileReviewToList(reviewedProfileHash, reviewHash)
		if (err != nil) { return false, err }

		return true, nil
	}
	if (reviewType == "Attribute"){

		if (len(reviewedHash) != 27){
			reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
			return false, errors.New("ReadReview returning invalid length reviewed Attribute hash: " + reviewedHashHex)
		}

		reviewedAttributeHash := [27]byte(reviewedHash)

		err = badgerDatabase.AddProfileAttributeReviewToList(reviewedAttributeHash, reviewHash)
		if (err != nil) { return false, err }

		return true, nil
	}
	if (reviewType == "Message"){

		if (len(reviewedHash) != 26){
			reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
			return false, errors.New("ReadReview returning invalid length reviewed Message hash: " + reviewedHashHex)
		}

		reviewedMessageHash := [26]byte(reviewedHash)

		err = badgerDatabase.AddMessageReviewToList(reviewedMessageHash, reviewHash)
		if (err != nil) { return false, err }

		return true, nil
	}

	return false, errors.New("ReadReview returning invalid reviewType: " + reviewType)
}

//Outputs:
//	-bool: Review exists
//	-[]byte: Review bytes
//	-map[string]string: Review map
//	-error
func GetModeratorNewestIdentityReview(moderatorIdentityHash [16]byte, reviewedIdentityHash [16]byte, networkType byte)(bool, []byte, map[string]string, error){

	isValid, err := identity.VerifyIdentityHash(moderatorIdentityHash, true, "Moderator")
	if (err != nil) { return false, nil, nil, err }
	if (isValid == false){
		moderatorIdentityHashHex := encoding.EncodeBytesToHexString(moderatorIdentityHash[:])
		return false, nil, nil, errors.New("GetModeratorNewestIdentityReview called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex)
	}

	isValid, err = identity.VerifyIdentityHash(reviewedIdentityHash, false, "")
	if (err != nil) { return false, nil, nil, err }
	if (isValid == false){
		reviewedIdentityHashHex := encoding.EncodeBytesToHexString(reviewedIdentityHash[:])
		return false, nil, nil, errors.New("GetModeratorNewestIdentityReview called with invalid reviewedIdentityHash: " + reviewedIdentityHashHex)
	}

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

	anyReviewsExist, reviewHashesList, err := badgerDatabase.GetIdentityReviewsList(reviewedIdentityHash)
	if (err != nil) { return false, nil, nil, err }
	if (anyReviewsExist == false){
		return false, nil, nil, nil
	}

	reviewsList := make([]readReviews.ReviewWithHash, 0)

	for _, reviewHash := range reviewHashesList{

		exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
		if (err != nil) { return false, nil, nil, err }
		if (exists == false){
			// Review has been deleted. This missing entry will be removed automatically.
			continue
		}

		newReviewObject := readReviews.ReviewWithHash{
			ReviewHash: reviewHash,
			ReviewBytes: reviewBytes,
		}

		reviewsList = append(reviewsList, newReviewObject)
	}

	verdictExists, reviewBytes, newestReviewMap, err := readReviews.GetModeratorNewestIdentityReviewFromReviewsList(reviewsList, moderatorIdentityHash, reviewedIdentityHash, networkType)
	if (err != nil) { return false, nil, nil, err }
	if (verdictExists == false){
		return false, nil, nil, nil
	}
	return true, reviewBytes, newestReviewMap, nil
}


//Outputs:
//	-bool: Profile verdict exists (At least 1 attribute ban, or a full profile approve/ban review exists)
//	-string: Profile verdict "Ban"/"Approve" (incorporates full profile reviews and attribute bans)
//	-bool: Full profile review exists
//	-[]byte: Full profile review bytes
//	-map[string]string: Full profile review map
//	-map[[27]byte][]byte: Map of attribute approve reviews (Attribute hash -> Attribute review)
//	-map[[27]byte][]byte: Map of attribute ban reviews (Attribute hash -> Attribute review)
//	-error
func GetModeratorNewestProfileReviews(moderatorIdentityHash [16]byte, profileHash [28]byte, profileNetworkType byte, profileAttributeHashesList [][27]byte)(bool, string, bool, []byte, map[string]string, map[[27]byte][]byte, map[[27]byte][]byte, error){

	isValid := helpers.VerifyNetworkType(profileNetworkType)
	if (isValid == false){
		profileNetworkTypeString := helpers.ConvertByteToString(profileNetworkType)
		return false, "", false, nil, nil, nil, nil, errors.New("GetModeratorNewestProfileReviews called with invalid profileNetworkType: " + profileNetworkTypeString)
	}

	fullProfileReviewExists, fullProfileReviewBytes, fullProfileReviewMap, fullProfileReviewVerdict, fullProfileReviewTime, err := getModeratorNewestFullProfileReview(moderatorIdentityHash, profileHash, profileNetworkType)
	if (err != nil) { return false, "", false, nil, nil, nil, nil, err }

	// Full profile approvals can be overwritten by an attribute ban of any attribute within the profile
	// We check for attribute reviews

	//Map Structure: Attribute hash -> Attribute review bytes
	approvedAttributesMap := make(map[[27]byte][]byte)

	//Map Structure: Attribute hash -> Attribute review bytes
	bannedAttributesMap := make(map[[27]byte][]byte)

	for _, attributeHash := range profileAttributeHashesList{

		attributeReviewExists, attributeReviewType, attributeReviewBytes, _, attributeReviewVerdict, attributeReviewTime, err := GetModeratorNewestProfileAttributeReview(moderatorIdentityHash, attributeHash, profileNetworkType, false) 
		if (err != nil){ return false, "", false, nil, nil, nil, nil, err }
		if (attributeReviewExists == false){
			continue
		}

		if (attributeReviewType != "Attribute"){
			return false, "", false, nil, nil, nil, nil, errors.New("GetModeratorNewestProfileAttributeReview returning non-attribute review when integrateFullProfileApprovals == false")
		}

		if (attributeReviewVerdict == "Approve"){
			// Attribute approve reviews do not change full profile ban reviews
			approvedAttributesMap[attributeHash] = attributeReviewBytes
			continue
		}

		if (fullProfileReviewExists == true && fullProfileReviewVerdict == "Approve"){
			if (fullProfileReviewTime > attributeReviewTime){
				// This attribute ban was created before the moderator approved the full profile
				// It has therefore been overwritten
				continue 
			}
		}

		bannedAttributesMap[attributeHash] = attributeReviewBytes
	}

	if (len(bannedAttributesMap) > 0){

		// Profile is banned by moderator

		if (fullProfileReviewExists == true && fullProfileReviewVerdict == "Approve"){
			// An attribute was banned, which overwrites the full profile review

			return true, "Ban", false, nil, nil, approvedAttributesMap, bannedAttributesMap, nil
		}

		return true, "Ban", fullProfileReviewExists, fullProfileReviewBytes, fullProfileReviewMap, approvedAttributesMap, bannedAttributesMap, nil
	}

	if (fullProfileReviewExists == false){
		return false, "", false, nil, nil, approvedAttributesMap, bannedAttributesMap, nil
	}

	return true, fullProfileReviewVerdict, true, fullProfileReviewBytes, fullProfileReviewMap, approvedAttributesMap, bannedAttributesMap, nil
}


// This function does not integrate the moderator's attribute reviews
//Outputs:
//	-bool: Review exists
//	-[]byte: Review bytes
//	-map[string]string: Review map
//	-string: Review verdict
//	-int64: Review creation time
//	-error
func getModeratorNewestFullProfileReview(moderatorIdentityHash [16]byte, profileHash [28]byte, profileNetworkType byte)(bool, []byte, map[string]string, string, int64, error){

	isValid, err := identity.VerifyIdentityHash(moderatorIdentityHash, true, "Moderator")
	if (err != nil) { return false, nil, nil, "", 0, err }
	if (isValid == false){
		moderatorIdentityHashHex := encoding.EncodeBytesToHexString(moderatorIdentityHash[:])
		return false, nil, nil, "", 0, errors.New("getModeratorNewestFullProfileReview called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex)
	}

	isValid, err = readProfiles.VerifyProfileHash(profileHash, false, "", true, false)
	if (err != nil){ return false, nil, nil, "", 0, err }
	if (isValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return false, nil, nil, "", 0, errors.New("getModeratorNewestFullProfileReview called with invalid profileHash: " + profileHashHex)
	}

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

	anyReviewsExist, reviewHashesList, err := badgerDatabase.GetProfileReviewsList(profileHash)	
	if (err != nil) { return false, nil, nil, "", 0, err }
	if (anyReviewsExist == false){
		return false, nil, nil, "", 0, nil
	}

	reviewsList := make([]readReviews.ReviewWithHash, 0)

	for _, reviewHash := range reviewHashesList{

		exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
		if (err != nil) { return false, nil, nil, "", 0, err }
		if (exists == false){
			// Review has been deleted. This missing entry will be removed automatically.
			continue
		}

		reviewObject := readReviews.ReviewWithHash{
			ReviewHash: reviewHash,
			ReviewBytes: reviewBytes,
		}

		reviewsList = append(reviewsList, reviewObject)
	}

	reviewExists, reviewBytes, reviewMap, reviewVerdict, reviewTime, err := readReviews.GetModeratorNewestProfileReviewFromReviewsList(reviewsList, moderatorIdentityHash, profileHash, profileNetworkType)
	if (err != nil) { return false, nil, nil, "", 0, err }
	if (reviewExists == false){
		return false, nil, nil, "", 0, nil
	}

	return true, reviewBytes, reviewMap, reviewVerdict, reviewTime, nil
}


//Outputs:
//	-bool: Review exists
//	-string: Review Type ("Profile"/"Attribute")
//	-[]byte: Review bytes
//	-map[string]string: Review map
//	-string: Review verdict
//	-int64: Time of review
//	-error
func GetModeratorNewestProfileAttributeReview(moderatorIdentityHash [16]byte, attributeHash [27]byte, attributeNetworkType byte, integrateFullProfileApprovals bool)(bool, string, []byte, map[string]string, string, int64, error){

	isValid, err := identity.VerifyIdentityHash(moderatorIdentityHash, true, "Moderator")
	if (err != nil) { return false, "", nil, nil, "", 0, err }
	if (isValid == false){
		moderatorIdentityHashHex := encoding.EncodeBytesToHexString(moderatorIdentityHash[:])
		return false, "", nil, nil, "", 0, errors.New("GetModeratorNewestProfileAttributeReview called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex)
	}

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

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

	//Outputs:
	//	-bool: Attribute review exists
	//	-[]byte: Review bytes
	//	-map[string]string: Review map
	//	-string: Review verdict
	//	-int64: Review creation time
	//	-error
	getModeratorNewestAttributeReview := func()(bool, []byte, map[string]string, string, int64, error){

		anyReviewsExist, reviewHashesList, err := badgerDatabase.GetProfileAttributeReviewsList(attributeHash)
		if (err != nil) { return false, nil, nil, "", 0, err }
		if (anyReviewsExist == false){
			return false, nil, nil, "", 0, nil
		}

		reviewsList := make([]readReviews.ReviewWithHash, 0)

		for _, reviewHash := range reviewHashesList{

			exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
			if (err != nil) { return false, nil, nil, "", 0, err }
			if (exists == false){
				// Review has been deleted. This missing entry will be removed automatically.
				continue
			}

			reviewObject := readReviews.ReviewWithHash{
				ReviewHash: reviewHash,
				ReviewBytes: reviewBytes,
			}

			reviewsList = append(reviewsList, reviewObject)
		}

		attributeReviewExists, attributeReviewBytes, attributeReviewMap, attributeReviewVerdict, attributeReviewTime, err := readReviews.GetModeratorNewestProfileAttributeReviewFromReviewsList(reviewsList, moderatorIdentityHash, attributeHash, attributeNetworkType)
		if (err != nil) { return false, nil, nil, "", 0, err }	
		if (attributeReviewExists == false){
			return false, nil, nil, "", 0, nil
		}

		return true, attributeReviewBytes, attributeReviewMap, attributeReviewVerdict, attributeReviewTime, nil
	}

	attributeReviewExists, attributeReviewBytes, attributeReviewMap, attributeReviewVerdict, attributeReviewTime, err := getModeratorNewestAttributeReview()
	if (err != nil) { return false, "", nil, nil, "", 0, err }

	if (integrateFullProfileApprovals == false){

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

		return true, "Attribute", attributeReviewBytes, attributeReviewMap, attributeReviewVerdict, attributeReviewTime, nil
	}

	if (attributeReviewExists == true && attributeReviewVerdict == "Approve"){

		// The user approved the attribute
		// We don't have to check for full profile approvals, because they would not change this verdict

		return true, "Attribute", attributeReviewBytes, attributeReviewMap, "Approve", attributeReviewTime, nil
	}

	// Now we check for full profile approvals
	// A full profile approval is equivalent to an attribute approval for all attributes within the profile
	// attributeProfileHashesList is a list of all profile hashes for profiles which contain the attribute

	anyExist, attributeProfileHashesList, err := badgerDatabase.GetAttributeProfilesList(attributeHash)
	if (err != nil) { return false, "", nil, nil, "", 0, err }
	if (anyExist == true){

		for _, profileHash := range attributeProfileHashesList{

			// We are only checking for full profile approvals
			// We don't need to check for profile attribute bans, because we are only concerned with the provided attribute's status
			// A full profile approval approves all attributes.
			// If a user bans a full profile, it does not change the status of any of their attribute approvals of the profile

			fullProfileReviewExists, fullProfileReviewBytes, fullProfileReviewMap, fullProfileVerdict, fullProfileVerdictTime, err := getModeratorNewestFullProfileReview(moderatorIdentityHash, profileHash, attributeNetworkType)
			if (err != nil){ return false, "", nil, nil, "", 0, err }
			if (fullProfileReviewExists == true && fullProfileVerdict == "Approve"){

				if (attributeReviewExists == true && fullProfileVerdictTime < attributeReviewTime){
					// The moderator banned the attribute after approving the full profile
					// The profile approval is therefore disregarded.
					continue
				}
				// The moderator approved the full profile after banning the attribute, thus, the attribute is approved

				return true, "Profile", fullProfileReviewBytes, fullProfileReviewMap, "Approve", fullProfileVerdictTime, nil
			}
		}
	}

	if (attributeReviewExists == true){
		// We could not find any full profile approvals that undo the attribute ban
		return true, "Attribute", attributeReviewBytes, attributeReviewMap, "Ban", attributeReviewTime, nil
	}

	return false, "", nil, nil, "", 0, nil
}


//Outputs:
//	-bool: Review exists
//	-[]byte: Review bytes
//	-map[string]string: Review map
//	-error
func GetModeratorNewestMessageReview(moderatorIdentityHash [16]byte, messageHash [26]byte, messageNetworkType byte, messageCipherKey [25]byte)(bool, []byte, map[string]string, error){

	isValid, err := identity.VerifyIdentityHash(moderatorIdentityHash, true, "Moderator")
	if (err != nil) { return false, nil, nil, err }
	if (isValid == false){
		moderatorIdentityHashHex := encoding.EncodeBytesToHexString(moderatorIdentityHash[:])
		return false, nil, nil, errors.New("GetModeratorNewestMessageReview called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex)
	}

	isValid = helpers.VerifyNetworkType(messageNetworkType)
	if (isValid == false){
		messageNetworkTypeString := helpers.ConvertByteToString(messageNetworkType)
		return false, nil, nil, errors.New("GetModeratorNewestMessageReview called with invalid messageNetworkType: " + messageNetworkTypeString)
	}

	anyReviewsExist, reviewHashesList, err := badgerDatabase.GetMessageReviewsList(messageHash)
	if (err != nil) { return false, nil, nil, err }
	if (anyReviewsExist == false){
		return false, nil, nil, nil
	}

	reviewsList := make([]readReviews.ReviewWithHash, 0)

	for _, reviewHash := range reviewHashesList{

		exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
		if (err != nil) { return false, nil, nil, err }
		if (exists == false){
			// Review has been deleted. This missing entry will be removed automatically.
			continue
		}

		reviewObject := readReviews.ReviewWithHash{
			ReviewHash: reviewHash,
			ReviewBytes: reviewBytes,
		}

		reviewsList = append(reviewsList, reviewObject)
	}

	reviewExists, reviewBytes, reviewMap, _, err := readReviews.GetModeratorNewestMessageReviewFromReviewsList(reviewsList, moderatorIdentityHash, messageHash, messageNetworkType, messageCipherKey)
	if (err != nil) { return false, nil, nil, err }
	if (reviewExists == false){
		return false, nil, nil, nil
	}

	return true, reviewBytes, reviewMap, nil
}

// This function returns all reviews by a provided moderator, omitting older reviews for the same reviewed hashes
// Will omit None reviews
//Outputs:
//	-[][]byte: List of reviews
//	-error
func GetAllNewestReviewsCreatedByModerator(moderatorIdentityHash [16]byte, reviewType string, networkType byte)([][]byte, error){

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

	exists, reviewHashesList, err := badgerDatabase.GetReviewerReviewHashesList(moderatorIdentityHash, reviewType)
	if (err != nil) { return nil, err }
	if (exists == false){
		emptyList := make([][]byte, 0)
		return emptyList, nil
	}

	reviewsList := make([]readReviews.ReviewWithHash, 0)

	for _, reviewHash := range reviewHashesList{

		exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
		if (err != nil) { return nil, err }
		if (exists == false){
			// Review has been deleted. The missing entry will be removed automatically in the background.
			continue
		}

		reviewObject := readReviews.ReviewWithHash{
			ReviewHash: reviewHash,
			ReviewBytes: reviewBytes,
		}

		reviewsList = append(reviewsList, reviewObject)
	}

	reviewsMap, err := readReviews.GetNewestModeratorReviewsMapFromReviewsList(reviewsList, moderatorIdentityHash, networkType, true, reviewType)
	if (err != nil) { return nil, err }

	newestReviewsList := helpers.GetListOfMapValues(reviewsMap)

	return newestReviewsList, nil
}


// This function returns the number of ban advocates for a user
// This returns all advocates, banned or not
//Outputs:
//	-bool: Downloading required reviews and profiles
//	-int: Number of ban advocates
//	-error
func GetNumberOfBanAdvocatesForIdentity(identityHash [16]byte, networkType byte)(bool, int, error){

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

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) { return false, 0, err }
	if (appNetworkType != networkType){
		// We are not downloading reviews for this network type.
		return false, 0, nil
	}

	downloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineIdentityVerdicts(identityHash)
	if (err != nil) { return false, 0, err }
	if (downloadingRequiredReviews == false){
		return false, 0, nil
	}

	banAdvocatesMap, err := GetIdentityBanAdvocatesMap(identityHash, networkType)
	if (err != nil) { return false, 0, err }

	numberOfBanAdvocates := len(banAdvocatesMap)

	return true, numberOfBanAdvocates, nil
}


//Outputs:
//	-bool: Message metadata is known
//	-bool: Downloading required reviews and profiles
//	-int: Number of reviewers
//	-error
func GetNumberOfMessageReviewers(messageHash [26]byte)(bool, bool, int, error){

	metadataExists, _, messageNetworkType, _, messageInbox, messageCipherKeyHash, err := contentMetadata.GetMessageMetadata(messageHash)
	if (err != nil) { return false, false, 0, err }
	if (metadataExists == false){
		return false, false, 0, nil
	}

	downloadingRequiredData, err := backgroundDownloads.CheckIfAppCanDetermineMessageVerdict(messageNetworkType, messageInbox, true, messageHash)
	if (err != nil) { return false, false, 0, err }
	if (downloadingRequiredData == false){
		return true, false, 0, nil
	}

	approveAdvocatesMap, banAdvocatesMap, err := GetMessageVerdictMaps(messageHash, messageNetworkType, messageCipherKeyHash)
	if (err != nil) { return false, false, 0, err }

	numberOfReviewers := len(approveAdvocatesMap) + len(banAdvocatesMap)

	return true, true, numberOfReviewers, nil
}

// This function will integrate attribute bans
//Outputs:
//	-bool: Profile metadata is known
//	-bool: Downloading required reviews and profiles
//	-int: Number of reviewers
//	-error
func GetNumberOfProfileReviewers(profileHash [28]byte)(bool, bool, int, error){

	metadataExists, _, profileNetworkType, profileAuthor, _, profileIsDisabled, _, profileAttributeHashesMap, err := contentMetadata.GetProfileMetadata(profileHash)
	if (err != nil) { return false, false, 0, err }
	if (metadataExists == false){
		return false, false, 0, nil
	}
	if (profileIsDisabled == true){
		// Disabled profiles cannot be reviewed
		return true, true, 0, nil
	}

	appNetworkType, err := getAppNetworkType.GetAppNetworkType()
	if (err != nil) { return false, false, 0, err }
	if (appNetworkType != profileNetworkType){
		// We are not downloading reviews for this network type.
		return true, false, 0, nil
	}

	downloadingRequiredReviews, err := backgroundDownloads.CheckIfAppCanDetermineIdentityVerdicts(profileAuthor)
	if (err != nil) { return false, false, 0, err }
	if (downloadingRequiredReviews == false){
		return true, false, 0, nil
	}

	// profileAttributeHashesMap is a map whose values are the attribute hashes of the profile
	profileAttributeHashesList := helpers.GetListOfMapValues(profileAttributeHashesMap)

	approveAdvocatesMap, banAdvocatesMap, err := GetProfileVerdictMaps(profileHash, profileNetworkType, true, profileAttributeHashesList)
	if (err != nil) { return false, false, 0, err }

	numberOfReviewers := len(approveAdvocatesMap) + len(banAdvocatesMap)

	return true, true, numberOfReviewers, nil
}

// This function will go through all reviews for an identity and return the ban advocates
//Outputs:
//	-map[[16]byte]int64: Ban advocate identity hash -> Time of ban
//	-error
func GetIdentityBanAdvocatesMap(identityHash [16]byte, networkType byte)(map[[16]byte]int64, error){

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

	exists, reviewHashesList, err := badgerDatabase.GetIdentityReviewsList(identityHash)
	if (err != nil) { return nil, err }
	if (exists == false){
		emptyMap := make(map[[16]byte]int64)
		return emptyMap, nil
	}

	reviewsList := make([]readReviews.ReviewWithHash, 0)

	for _, reviewHash := range reviewHashesList{

		exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
		if (err	!= nil) { return nil, err }
		if (exists == false){
			// Review must have been deleted. The missing review entry will be pruned automatically
			continue
		}

		reviewObject := readReviews.ReviewWithHash{
			ReviewHash: reviewHash,
			ReviewBytes: reviewBytes,
		}

		reviewsList = append(reviewsList, reviewObject)
	}

	banAdvocatesMap, err := readReviews.GetIdentityBanAdvocatesMapFromReviewsList(reviewsList, identityHash, networkType)
	if (err != nil) { return nil, err }

	return banAdvocatesMap, nil
}

// This function will go through all reviews for a profile and return the approve and ban advocates
// If integrateAttributeBans == false, we will only check for full profile approvals/bans
//Outputs:
//	-map[[16]byte]int64: Approve advocates map (Moderator identity hash -> Time of review)
//	-map[[16]byte]int64: Ban advocates map (Moderator identity hash -> Time of review)
//	-error
func GetProfileVerdictMaps(profileHash [28]byte, profileNetworkType byte, integrateAttributeBans bool, attributeHashesList [][27]byte)(map[[16]byte]int64, map[[16]byte]int64, error){

	isValid := helpers.VerifyNetworkType(profileNetworkType)
	if (isValid == false){
		profileNetworkTypeString := helpers.ConvertByteToString(profileNetworkType)
		return nil, nil, errors.New("GetProfileVerdictMaps called with invalid profileNetworkType: " + profileNetworkTypeString)
	}

	exists, reviewHashesList, err := badgerDatabase.GetProfileReviewsList(profileHash)
	if (err != nil) { return nil, nil, err }
	if (exists == false){
		emptyMapA := make(map[[16]byte]int64)
		emptyMapB := make(map[[16]byte]int64)

		return emptyMapA, emptyMapB, nil
	}

	reviewsList := make([]readReviews.ReviewWithHash, 0)

	for _, reviewHash := range reviewHashesList{

		exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
		if (err	!= nil) { return nil, nil, err }
		if (exists == false){
			// Review has been deleted. The missing review entry will be pruned automatically.
			continue
		}

		reviewObject := readReviews.ReviewWithHash{
			ReviewHash: reviewHash,
			ReviewBytes: reviewBytes,
		}

		reviewsList = append(reviewsList, reviewObject)
	}

	approveAdvocatesMap, banAdvocatesMap, err := readReviews.GetProfileVerdictMapsFromReviewsList(reviewsList, profileHash, profileNetworkType)
	if (err != nil) { return nil, nil, err }

	if (integrateAttributeBans == false){
		return approveAdvocatesMap, banAdvocatesMap, nil
	}

	// Now we check for attribute ban advocates
	// A ban of any attribute within the profile is equivalent to a profile ban
	// attributeHashesList is a list of all attribute hashes within the provided profile

	integrateProfileAttributeBans := func()error{

		for _, attributeHash := range attributeHashesList{

			// We have to check for full profile reviews of all profiles by the user that contain this attribute
			// Any of these full profile approvals are equivalent to approving all profile attributes
		
			getAttributeProfileHashesList := func()([][28]byte, error){
		
				exists, attributeProfilesList, err := badgerDatabase.GetAttributeProfilesList(attributeHash)
				if (err != nil) { return nil, err }
				if (exists == false){
					emptyList := make([][28]byte, 0)
					return emptyList, nil
				}

				// We can omit the current profileHash, because we are already checking it
				attributeProfileHashesList, _ := helpers.DeleteAllMatchingItemsFromList(attributeProfilesList, profileHash)	

				return attributeProfileHashesList, nil
			}

			attributeProfileHashesList, err := getAttributeProfileHashesList()
			if (err != nil) { return err }

			_, attributeBanAdvocatesMap, err := GetProfileAttributeVerdictMaps(attributeHash, profileNetworkType, true, attributeProfileHashesList)
			if (err != nil) { return err }

			for moderatorIdentityHash, attributeBanTime := range attributeBanAdvocatesMap{

				existingApproveTime, exists := approveAdvocatesMap[moderatorIdentityHash]
				if (exists == true){
					if (existingApproveTime < attributeBanTime){
						// The moderator banned a profile attribute after approving the profile
						delete(approveAdvocatesMap, moderatorIdentityHash)
						banAdvocatesMap[moderatorIdentityHash] = attributeBanTime

						// We don't have to check for any more attribute bans
						// They can only cause an full profile approval to change to a ban

						return nil
					}
				}
			}
		}

		return nil	
	}

	err = integrateProfileAttributeBans()
	if (err != nil) { return nil, nil, err }

	return approveAdvocatesMap, banAdvocatesMap, nil
}

// This function will go through all reviews for a profile attribute and return the approve and ban advocates
// This also finds full profile approve reviews and treats them as approvals for all attributes within the profile
//Outputs:
//	-map[[16]byte]int64: Approve advocates map (Moderator identity hash -> Time of approval)
//	-map[[16]byte]int64: Ban advocates map (Moderator identity hash -> Time of ban)
//	-error
func GetProfileAttributeVerdictMaps(attributeHash [27]byte, attributeNetworkType byte, integrateFullProfileApprovals bool, attributeProfileHashesList [][28]byte)(map[[16]byte]int64, map[[16]byte]int64, error){

	isValid := helpers.VerifyNetworkType(attributeNetworkType)
	if (isValid == false){
		attributeNetworkTypeString := helpers.ConvertByteToString(attributeNetworkType)
		return nil, nil, errors.New("GetProfileAttributeVerdictMaps called with invalid attributeNetworkType: " + attributeNetworkTypeString)
	}

	exists, reviewHashesList, err := badgerDatabase.GetProfileAttributeReviewsList(attributeHash)
	if (err != nil) { return nil, nil, err }
	if (exists == false){
		emptyMapA := make(map[[16]byte]int64)
		emptyMapB := make(map[[16]byte]int64)

		return emptyMapA, emptyMapB, nil
	}

	reviewsList := make([]readReviews.ReviewWithHash, 0)

	for _, reviewHash := range reviewHashesList{

		exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
		if (err	!= nil) { return nil, nil, err }
		if (exists == false){
			// Review has been deleted. The missing review entry will be pruned automatically
			continue
		}

		reviewObject := readReviews.ReviewWithHash{
			ReviewHash: reviewHash,
			ReviewBytes: reviewBytes,
		}

		reviewsList = append(reviewsList, reviewObject)
	}

	approveAdvocatesMap, banAdvocatesMap, err := readReviews.GetProfileAttributeVerdictMapsFromReviewsList(reviewsList, attributeHash, attributeNetworkType)
	if (err != nil) { return nil, nil, err }

	if (integrateFullProfileApprovals == false){
		return approveAdvocatesMap, banAdvocatesMap, nil
	}

	// Now we check for full profile approve advocates
	// A full profile approval is equivalent to an attribute approval for all attributes within the profile
	// attributeProfileHashesList is a list of all profile hashes for profiles which contain the attribute

	integrateProfileApprovals := func()error{

		for _, profileHash := range attributeProfileHashesList{

			// We are only checking for full profile approvals
			// We don't need to check for profile attribute bans, because we are only concerned with the provided attribute's status
			// A full profile approval approves all attributes.
			// If the user bans a profile's attribute after approving the full profile, the other attributes are still considered approved by the user

			fullProfileApproveAdvocatesMap, _, err := GetProfileVerdictMaps(profileHash, attributeNetworkType, false, nil)
			if (err != nil) { return err }

			for moderatorIdentityHash, fullProfileApproveTime := range fullProfileApproveAdvocatesMap{

				existingBanTime, exists := banAdvocatesMap[moderatorIdentityHash]
				if (exists == true){
					if (existingBanTime < fullProfileApproveTime){
						// The moderator approved the full profile after banning the attribute
						delete(banAdvocatesMap, moderatorIdentityHash)
						approveAdvocatesMap[moderatorIdentityHash] = fullProfileApproveTime

						// We don't have to check for any more full profile approvals
						// They can only cause an attribute ban to change to an approval

						return nil
					}
				}
			}
		}

		return nil	
	}

	err = integrateProfileApprovals()
	if (err != nil) { return nil, nil, err }

	return approveAdvocatesMap, banAdvocatesMap, nil
}



// This function will go through all reviews for a message and return the approve and ban advocates
// We use the provided message CipherKeyHash to ensure the reviewers have seen the message
//Outputs:
//	-map[[16]byte]int64: Approve advocate identity hash -> Time of approval
//	-map[[16]byte]int64: Ban advocate identity hash -> Time of ban
//	-error
func GetMessageVerdictMaps(messageHash [26]byte, messageNetworkType byte, messageCipherKeyHash [25]byte)(map[[16]byte]int64, map[[16]byte]int64, error){

	isValid := helpers.VerifyNetworkType(messageNetworkType)
	if (isValid == false){
		messageNetworkTypeString := helpers.ConvertByteToString(messageNetworkType)
		return nil, nil, errors.New("GetMessageVerdictMaps called with invalid messageNetworkType: " + messageNetworkTypeString)
	}

	exists, reviewHashesList, err := badgerDatabase.GetMessageReviewsList(messageHash)
	if (err != nil) { return nil, nil, err }
	if (exists == false){

		// There are no reviews for this message

		emptyMapA := make(map[[16]byte]int64)
		emptyMapB := make(map[[16]byte]int64)

		return emptyMapA, emptyMapB, nil
	}

	reviewsList := make([]readReviews.ReviewWithHash, 0)

	for _, reviewHash := range reviewHashesList{

		exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
		if (err	!= nil) { return nil, nil, err }
		if (exists == false){
			// Review has been deleted. Missing review entry will be pruned automatically
			continue
		}

		reviewObject := readReviews.ReviewWithHash{
			ReviewHash: reviewHash,
			ReviewBytes: reviewBytes,
		}

		reviewsList = append(reviewsList, reviewObject)
	}

	approveAdvocatesMap, banAdvocatesMap, err := readReviews.GetMessageVerdictMapsFromReviewsList(reviewsList, messageHash, messageNetworkType, messageCipherKeyHash)
	if (err != nil) { return nil, nil, err }

	return approveAdvocatesMap, banAdvocatesMap, nil
}

// We use this function to find a valid message cipher key to decrypt a message for moderation
// We use the message's CipherKeyHash to verify that the author of the review has seen the decrypted message
// If the cipher key does not decrypt the message, then we know the author of the review and the message are malicious
//Outputs:
//	-bool: Message cipher key found
//	-[32]byte: Message Cipher key
//	-error
func GetMessageCipherKeyFromAnyReview(messageHash [26]byte, messageNetworkType byte, messageCipherKeyHash [25]byte)(bool, [32]byte, error){

	isValid := helpers.VerifyNetworkType(messageNetworkType)
	if (isValid == false){
		messageNetworkTypeString := helpers.ConvertByteToString(messageNetworkType)
		return false, [32]byte{}, errors.New("GetMessageCipherKeyFromAnyReview called with invalid messageNetworkType: " + messageNetworkTypeString)
	}

	exists, reviewHashesList, err := badgerDatabase.GetMessageReviewsList(messageHash)
	if (err != nil) { return false, [32]byte{}, err }
	if (exists == false){
		return false, [32]byte{},  nil
	}
	if (len(reviewHashesList) == 0){
		return false, [32]byte{}, nil
	}

	for _, reviewHash := range reviewHashesList{

		exists, reviewBytes, err := badgerDatabase.GetReview(reviewHash)
		if (err != nil) { return false, [32]byte{}, err }
		if (exists == false){
			// Message must have been deleted.
			// The missing reviewsList entry will be removed automatically.
			continue
		}

		ableToRead, _, reviewNetworkType, _, _, reviewType, reviewedHash, _, reviewMap, err := readReviews.ReadReview(false, reviewBytes)
		if (err != nil) { return false, [32]byte{}, err }
		if (ableToRead == false){
			return false, [32]byte{}, errors.New("Database corrupt: Contains invalid review.")
		}
		if (reviewNetworkType != messageNetworkType){
			// The author of this review is malicious
			// We will not try to get the message cipher key from the review, even if it exists
			// This is because we want all moderators to see the same messages to review, regardless of if they were previously on a different network.
			continue
		}
		if (reviewType != "Message") {
			return false, [32]byte{}, errors.New("Corrupt database: ReviewType does not match reviews list.")
		}

		areEqual := bytes.Equal(reviewedHash, messageHash[:])
		if (areEqual == false){
			return false, [32]byte{}, errors.New("Corrupt database: Reviewed message hash does not match reviews list.")
		}

		messageCipherKeyString, exists := reviewMap["MessageCipherKey"]
		if (exists == false) {
			return false, [32]byte{}, errors.New("Corrupt database: Contains review missing MessageCipherKey")
		}

		messageCipherKey, err := readMessages.ReadMessageCipherKeyHex(messageCipherKeyString)
		if (err != nil){
			return false, [32]byte{}, errors.New("Corrupt database: Contains review with invalid MessageCipherKey: " + messageCipherKeyString + ". Reason: " + err.Error())
		}

		currentMessageCipherKeyHash, err := readMessages.ConvertMessageCipherKeyToCipherKeyHash(messageCipherKey)
		if (err != nil){ return false, [32]byte{}, err }

		if (currentMessageCipherKeyHash != messageCipherKeyHash){
			// Reviewer is malicious.
			continue
		}

		return true, messageCipherKey, nil
	}

	// No valid reviews found for message

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


