
// myUnreviewed provides functions to get message, profile, and attribute hashes the user has not yet reviewed

package myUnreviewed

// Each content to review has a priority
// Content that has been reviewed by the fewest moderators will have the highest priority
// Content that has been sufficiently reviewed by other moderators will have a lower priority
// Unreviewed content will be retained by the client until it expires from the network

//TODO: Add filter for ProfileLanguage, so user will only review content they understand
//TODO: Add higher priority for reported profiles?
//TODO: Add limit where if it has found enough options to review, break
//		-This means we will only need to search through a maximum number of contents before we settle on one that is the highest priority
//		-It may not be the highest priority out of all contents, but it will be the highest priority out of a subset of contents
//		-This would make the retrieval faster
//TODO: Extract errantProfiles from identity reviews and treat them like profile reports
//TODO: Add a toggle to consider/not consider profiles/messages reviewed if we have banned the author
//TODO: Build a cache to store the highest priority unreviewed content hashes
// 		-This way we only have to repeat the process after the highest priority unreviewed content has been reviewed

import "seekia/internal/moderation/myReviews"
import "seekia/internal/moderation/verifiedVerdict"
import "seekia/internal/moderation/mySkippedContent"
import "seekia/internal/moderation/myHiddenContent"
import "seekia/internal/profiles/profileStorage"
import "seekia/internal/profiles/readProfiles"
import "seekia/internal/messaging/chatMessageStorage"
import "seekia/internal/myRanges"
import "seekia/internal/badgerDatabase"
import "seekia/internal/helpers"

import "strings"
import "errors"
import "slices"

//Outputs:
//	-bool: Any unreviewed message hash exists
//	-[26]byte: Message Hash
//	-error
func GetMyHighestPriorityUnreviewedMessageHash(networkType byte, imageOrText string)(bool, [26]byte, error){

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

	if (imageOrText != "Image" && imageOrText != "Text"){
		return false, [26]byte{}, errors.New("GetMyHighestPriorityUnreviewedMessageHash called with invalid imageOrText: " + imageOrText)
	}

	allReviewedMessageHashesList, err := badgerDatabase.GetAllReviewedMessageHashes()
	if (err != nil) { return false, [26]byte{}, err }

	allReportedMessageHashesList, err := badgerDatabase.GetAllReportedMessageHashes()
	if (err != nil) { return false, [26]byte{}, err }

	allMessageHashesList := helpers.CombineTwoListsAndAvoidDuplicates(allReviewedMessageHashesList, allReportedMessageHashesList)

	myModeratorIdentityExists, myReviewedMessageHashesList, err := myReviews.GetMyReviewedMessageHashesList(networkType)
	if (err != nil) { return false, [26]byte{}, err }
	if (myModeratorIdentityExists == false) {
		return false, [26]byte{}, errors.New("GetMyHighestPriorityUnreviewedMessageHash called when my moderator identity does not exist.")
	}

	myIdentityExists, myBannedIdentityHashesList, err := myReviews.GetMyBannedIdentityHashesList(networkType)
	if (err != nil) { return false, [26]byte{}, err }
	if (myIdentityExists == false) {
		return false, [26]byte{}, errors.New("My moderator identity not found after being found already.")
	}

	leastReviewedMessageFound := false
	var leastReviewedMessageHash [26]byte
	leastReviewedMessageNumberOfReviews := 0

	// We will only serve skipped messages if all messages remaining to review are skipped
	// This bool will be true, unless we encounter a non-skipped message
	// If we do, we will overwrite the existing least reviewed messageHash and reject all skipped messages going forward
	allMessagesAreSkipped := true

	for _, messageHash := range allMessageHashesList{

		iHaveReviewed := slices.Contains(myReviewedMessageHashesList, messageHash)
		if (iHaveReviewed == true){
			continue
		}

		messageMetadataIsKnown, _, messageNetworkType, messageInbox, _, downloadingRequiredReviews, parametersExist, _, numberApprove, numberBan, _, _, _, _, err := verifiedVerdict.GetVerifiedMessageVerdict(messageHash)
		if (err != nil) { return false, [26]byte{}, err }
		if (messageMetadataIsKnown == false){
			// We do not have the message downloaded. It is not eligible for review
			continue
		}
		if (messageNetworkType != networkType){
			// Message belongs to a different networkType.
			continue
		}
		if (downloadingRequiredReviews == false){
			// Message is not within our moderation range. Skip message
			continue
		}
		if (parametersExist == false){
			// Moderation parameters need to be downloaded to moderate content. Stop searching for messages.
			break
		}
		moderatorModeEnabled, isInMyModerationRange, err := myRanges.CheckIfMessageInboxIsWithinMyModerationRange(messageInbox)
		if (err != nil) { return false, [26]byte{}, err }
		if (moderatorModeEnabled == false){
			// Mode must have been disabled during generation.
			// No messages to review.
			return false, [26]byte{}, nil
		}
		if (isInMyModerationRange == false){
			// Message is outside of our moderation range. Skip it.
			continue
		}

		messageExists, cipherKeyFound, messageIsDecryptable, _, senderIdentityHash, messageCommunication, err := chatMessageStorage.GetDecryptedMessageForModeration(messageHash)
		if (err != nil) { return false, [26]byte{}, err }
		if (messageExists == false){
			// We cannot review messages which we do not have downloaded
			continue
		}
		if (cipherKeyFound == false){
			// We do not have a cipher key
			// We cannot view the message contents
			continue
		}
		if (messageIsDecryptable == false){
			// We cannot decrypt the message
			// Message author must be malicious
			// We should ban these kinds of messages automatically, and ban anyone who approves them
			continue
		}

		identityIsBanned := slices.Contains(myBannedIdentityHashesList, senderIdentityHash)
		if (identityIsBanned == true){
			// We already banned the author of the message
			// Review is not necessary 
			continue
		}

		getImageOrTextStatus := func()string{
			isImage := strings.HasPrefix(messageCommunication, ">!>Photo=")
			if (isImage == true){
				return "Image"
			}
			return "Text"
		}

		imageOrTextStatus := getImageOrTextStatus()
		if (imageOrTextStatus != imageOrText){
			// Message is not the imageOrText that we are interested in.
			continue
		}

		messageIsHidden, _, _, err := myHiddenContent.CheckIfMessageIsHidden(messageHash)
		if (err != nil) { return false, [26]byte{}, err }
		if (messageIsHidden == true){
			// The moderator has hidden this message.
			continue
		}

		messageIsSkipped, _, _, err := mySkippedContent.CheckIfMessageIsSkipped(messageHash)
		if (err != nil) { return false, [26]byte{}, err }
		if (messageIsSkipped == false){
			if (allMessagesAreSkipped == true){

				// This is the first non-skipped message we have encountered
				// This may not be the first message we have encountered
				// Thus, we must clear any previously found content
				leastReviewedMessageFound = false
				leastReviewedMessageHash = [26]byte{}
				leastReviewedMessageNumberOfReviews = 0

				allMessagesAreSkipped = false
			}
		} else {
			if (allMessagesAreSkipped == false){
				// We have some non-skipped content to review, so we will reject all skipped content
				continue
			}
		}

		numberOfReviewers := numberApprove + numberBan

		if (numberOfReviewers == 0 && messageIsSkipped == false){
			// Message is high priority, it has no reviews. We stop searching.
			return true, messageHash, nil
		}

		if (leastReviewedMessageFound == false || numberOfReviewers < leastReviewedMessageNumberOfReviews){
			leastReviewedMessageFound = true
			leastReviewedMessageHash = messageHash
			leastReviewedMessageNumberOfReviews = numberOfReviewers
		}
	}

	if (leastReviewedMessageFound == false){
		// Nothing left to review.
		return false, [26]byte{}, nil
	}

	return true, leastReviewedMessageHash, nil
}


// This function only returns full profiles that have been banned or reported, without reference to the specific attribute
// To approve these profiles, moderators must approve the full profile, which requires viewing all attributes of the profile, including canonical ones
// For profiles whose reviewed attributes have been specified, use GetMyHighestPriorityUnreviewedProfileAttributeHash instead
//Outputs:
//	-bool: Unreviewed profile exists
//	-[28]byte: Highest priority unreviewed profile hash
//	-error
func GetMyHighestPriorityUnreviewedProfileHash(profileType string, networkType byte)(bool, [28]byte, error){

	if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
		return false, [28]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileHash called with invalid profileType: " + profileType)
	}

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

	// For each profile, we must determine if we have reviewed it
	// If we have approved/banned the entire profile, then it is considered reviewed
	// If we have banned any attribute of the profile, then it is considered reviewed

	moderatorIdentityExists, myReviewedProfileHashesMap, err := myReviews.GetMyReviewedProfileHashesMap(profileType, networkType)
	if (err != nil) { return false, [28]byte{}, err }
	if (moderatorIdentityExists == false) {
		return false, [28]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileHash called when my moderator identity does not exist.")
	}

	myIdentityExists, myBannedIdentityHashesList, err := myReviews.GetMyBannedIdentityHashesList(networkType)
	if (err != nil) { return false, [28]byte{}, err }
	if (myIdentityExists == false) {
		return false, [28]byte{}, errors.New("My moderator identity not found after being found already.")
	}

	allReviewedProfileHashesList, err := badgerDatabase.GetAllReviewedProfileHashes()
	if (err != nil) { return false, [28]byte{}, err }

	allReportedProfileHashesList, err := badgerDatabase.GetAllReportedProfileHashes()
	if (err != nil) { return false, [28]byte{}, err }

	allProfileHashesList := helpers.CombineTwoListsAndAvoidDuplicates(allReviewedProfileHashesList, allReportedProfileHashesList)

	leastReviewedProfileHashFound := false
	var leastReviewedProfileHash [28]byte
	leastReviewedProfileHashReviewersCount := 0

	// We will only serve skipped profiles if all remaining profiles to review are skipped
	// This bool will be true, unless we encounter a non-skipped profile
	// If we do, we will overwrite the existing least reviewed profileHash and reject all skipped profiles going forward
	allProfilesAreSkipped := true

	for _, profileHash := range allProfileHashesList{

		_, iHaveReviewed := myReviewedProfileHashesMap[profileHash]
		if (iHaveReviewed == true){
			// We have already reviewed the profile, or banned one of its attributes
			continue
		}

		profileIsHidden, _, _, err := myHiddenContent.CheckIfProfileIsHidden(profileHash)
		if (err != nil) { return false, [28]byte{}, err }
		if (profileIsHidden == true){
			// The moderator has hidden this profile.
			continue
		}

		exists, _, err := profileStorage.GetStoredProfile(profileHash)
		if (err != nil) { return false, [28]byte{}, err }
		if (exists == false){
			// We need the profile bytes to review the profile
			continue
		}

		profileIsDisabled, profileMetadataIsKnown, _, profileNetworkType, profileAuthor, _, downloadingRequiredReviews, parametersExist, _, approveAdvocatesCount, banAdvocatesCount, _, _, _, _, _, _, _, _, fullProfileBanAdvocatesCount, _, _, err := verifiedVerdict.GetVerifiedProfileVerdict(profileHash)
		if (err != nil) { return false, [28]byte{}, err }
		if (profileIsDisabled == true){
			// No review is needed. Skip
			continue
		}
		if (profileMetadataIsKnown == false){
			// We do not have the profile downloaded, we cannot review it.
			// It must have been deleted after GetAllProfileHashes step.
			continue
		}
		if (profileNetworkType != networkType){
			// Profile belongs to a different networkType.
			continue
		}
		if (downloadingRequiredReviews == false){
			// Message is not within our moderation range. Skip profile
			continue
		}
		if (parametersExist == false){
			// Moderation parameters need to be downloaded to moderate content. Stop searching for profiles.
			return false, [28]byte{}, nil
		}

		profileIsReported := slices.Contains(allReportedProfileHashesList, profileHash)

		if (fullProfileBanAdvocatesCount == 0 && profileIsReported == false){
			// Nobody has banned/reported the full profile.
			// This means that the profile can be reviewed by reviewing attributes only
			// We skip this profile
			continue
		}

		moderatorModeEnabled, isWithinMyRange, err := myRanges.CheckIfIdentityHashIsWithinMyModerationRange(profileAuthor)
		if (err != nil) { return false, [28]byte{}, err }
		if (moderatorModeEnabled == false){
			// Mode must have been disabled after previous check. Return no profiles to review.
			return false, [28]byte{}, nil
		}
		if (isWithinMyRange == false){
			// Profile is outside of our moderation range, skip it.
			continue
		}

		iHaveBannedIdentity := slices.Contains(myBannedIdentityHashesList, profileAuthor)
		if (iHaveBannedIdentity == true){
			// We have banned the profile author
			// Reviewing the profile is not necessary
			continue
		}

		profileIsSkipped, _, _, err := mySkippedContent.CheckIfProfileIsSkipped(profileHash)
		if (err != nil) { return false, [28]byte{}, err }
		if (profileIsSkipped == false){
			if (allProfilesAreSkipped == true){

				// This is the first non-skipped profile we have encountered
				// This may not be the first profile we have encountered
				// Thus, we must clear any previously found profiles
				leastReviewedProfileHashFound = false
				leastReviewedProfileHash = [28]byte{}
				leastReviewedProfileHashReviewersCount = 0

				allProfilesAreSkipped = false
			}
		} else {
			if (allProfilesAreSkipped == false){
				// We have some non-skipped content to review, so we will reject all skipped content
				continue
			}
		}

		numberOfEligibleReviewers := approveAdvocatesCount + banAdvocatesCount

		if (approveAdvocatesCount == 0 && profileIsSkipped == false){

			// This profile has been either been reported or banned, and has no approvals by anyone eligible
			// We consider this the highest priority kind of profile.
			// Stop searching and return this profile
			return true, profileHash, nil
		}

		if (leastReviewedProfileHashFound == false || leastReviewedProfileHashReviewersCount < numberOfEligibleReviewers){
			leastReviewedProfileHashFound = true
			leastReviewedProfileHash = profileHash
			leastReviewedProfileHashReviewersCount = numberOfEligibleReviewers
		}
	}

	if (leastReviewedProfileHashFound == false){
		// No profiles to review
		return false, [28]byte{}, nil
	}

	return true, leastReviewedProfileHash, nil
}


//Outputs:
//	-bool: Any unreviewed profile attribute hash exists
//	-[27]byte: Profile attribute hash
//	-[28]byte: Profile hash that contained this attribute (there may be many, we will return the first one we encounter)
//	-[16]byte: Profile identity hash that authored the profile
//	-error
func GetMyHighestPriorityUnreviewedProfileAttributeHash(profileType string, attributeIdentifier int, networkType byte)(bool, [27]byte, [28]byte, [16]byte, error){

	if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
		return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileAttributeHash called with invalid profileType: " + profileType)
	}

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

	myIdentityExists, myReviewedProfileHashesMap, err := myReviews.GetMyReviewedProfileHashesMap(profileType, networkType)
	if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }
	if (myIdentityExists == false) {
		return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileAttributeHash called when my moderator identity does not exist.")
	}

	myIdentityExists, myReviewedAttributeHashesMap, err := myReviews.GetMyReviewedProfileAttributeHashesMap(networkType)
	if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }
	if (myIdentityExists == false) {
		return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileAttributeHash called when my moderator identity does not exist.")
	}

	myIdentityExists, myBannedIdentityHashesList, err := myReviews.GetMyBannedIdentityHashesList(networkType)
	if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }
	if (myIdentityExists == false) {
		return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetMyHighestPriorityUnreviewedProfileAttributeHash called when my moderator identity does not exist.")
	}

	allReportedAttributeHashesList, err := badgerDatabase.GetAllReportedProfileAttributeHashes()
	if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }

	allProfileHashesList, err := badgerDatabase.GetAllProfileHashes(profileType)
	if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }

	// This map will store the attribute hashes we have already checked
	// We need this because the same attribute may exist in multiple profiles
	encounteredAttributeHashesMap := make(map[[27]byte]struct{})

	// We use the below variables to find the least reviewed (highest priority) attribute hash
	leastReviewedAttributeHashFound := false
	var leastReviewedAttributeHash [27]byte
	var leastReviewedAttributeProfileHash [28]byte
	var leastReviewedAttributeIdentityHash [16]byte
	leastReviewedAttributeHashReviewsCount := 0

	// We will only serve skipped attributes if all remaining attributes to review are skipped
	// This bool will be true, unless we encounter a non-skipped attribute
	// If we do, we will overwrite the existing least reviewed attributeHash and reject all skipped attributes going forward
	allAttributesAreSkipped := true

	for _, profileHash := range allProfileHashesList{

		_, iHaveReviewed := myReviewedProfileHashesMap[profileHash]
		if (iHaveReviewed == true){
			// We have already reviewed this profile.
			// We don't need to review any of its component attributes
			continue
		}

		exists, _, err := profileStorage.GetStoredProfile(profileHash)
		if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }
		if (exists == false){
			// We need the profile bytes to review the profile
			// Profile must have been deleted after we called GetAllProfileHashes
			continue
		}

		profileIsDisabled, profileMetadataIsKnown, _, profileNetworkType, profileAuthor, profileAttributeHashesMap, downloadingRequiredReviews, parametersExist, _, _, _, _, _, attributeApproveAdvocateCountsMap, attributeBanAdvocateCountsMap, _, _, _, _, _, _, allAttributeBanAdvocatesMap, err := verifiedVerdict.GetVerifiedProfileVerdict(profileHash)
		if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }
		if (profileIsDisabled == true){
			// No review is needed. Skip
			continue
		}
		if (profileMetadataIsKnown == false){
			// We do not have the profile downloaded, we cannot review it.
			// It must have been deleted after GetAllProfileHashes step.
			continue
		}
		if (profileNetworkType != networkType){
			// Profile belongs to a different networkType.
			continue
		}
		if (downloadingRequiredReviews == false){
			// Profile is not within our moderation range. Skip message
			continue
		}
		if (parametersExist == false){
			// Moderation parameters need to be downloaded to moderate content. Stop searching for profile attributes.
			break
		}
		moderatorModeEnabled, isWithinMyRange, err := myRanges.CheckIfIdentityHashIsWithinMyModerationRange(profileAuthor)
		if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }
		if (moderatorModeEnabled == false){
			// Mode must have been disabled after previous check. Return no profiles to review.
			return false, [27]byte{}, [28]byte{}, [16]byte{}, nil
		}
		if (isWithinMyRange == false){
			// Profile is outside of our moderation range, skip it.
			continue
		}

		iHaveBannedIdentity := slices.Contains(myBannedIdentityHashesList, profileAuthor)
		if (iHaveBannedIdentity == true){
			// We have already banned the profile author
			continue
		}

		attributeHash, exists := profileAttributeHashesMap[attributeIdentifier]
		if (exists == false){
			// This profile does not have the attribute which we want to review
			continue
		}

		_, hasBeenEncountered := encounteredAttributeHashesMap[attributeHash]
		if (hasBeenEncountered == true){
			// We already dealt with this attribute
			continue
		}

		encounteredAttributeHashesMap[attributeHash] = struct{}{}

		_, iHaveReviewed = myReviewedAttributeHashesMap[attributeHash]
		if (iHaveReviewed == true){
			// We have already reviewed the attribute.
			continue
		}

		attributeIsHidden, _, _, err := myHiddenContent.CheckIfAttributeIsHidden(attributeHash)
		if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }
		if (attributeIsHidden == true){
			// The moderator has hidden this attribute.
			continue
		}

		// This will tell us if any moderators, banned or not, have banned the attribute
		checkIfAnyAttributeBanAdvocatesExist := func()bool{

			allAttributeBanAdvocatesList, exists := allAttributeBanAdvocatesMap[attributeIdentifier]
			if (exists == false){
				return false
			}
			if (len(allAttributeBanAdvocatesList) == 0){
				return false
			}
			return true
		}

		anyAttributeBanAdvocatesExist := checkIfAnyAttributeBanAdvocatesExist()

		if (anyAttributeBanAdvocatesExist == false){

			attributeIsReported := slices.Contains(allReportedAttributeHashesList, attributeHash)			

			if (attributeIsReported == false){

				// Nobody has banned/reported this attribute

				if (profileType != "Mate"){
					// We do not need to review attributes that are not banned for non-mate profiles
					continue
				}

				attributeProfileType, attributeIsCanonical, err := readProfiles.ReadAttributeHashMetadata(attributeHash)
				if (err != nil){ return false, [27]byte{}, [28]byte{}, [16]byte{}, err }
				if (attributeProfileType != profileType){
					return false, [27]byte{}, [28]byte{}, [16]byte{}, errors.New("GetVerifiedProfileVerdict returning attribute hash with different profileType than author.")
				}

				if (attributeIsCanonical == true){
					// We don't need to review it, even if profileType == Mate
					continue
				}

				// We see if the user has a newer profile
				// If they do, we don't need to review this profile

				anyProfileExists, _, newestProfileHash, _, _, _, err := profileStorage.GetNewestUserProfile(profileAuthor, networkType)
				if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }
				if (anyProfileExists == false){
					// The user's profile must have been deleted after we performed GetAllProfileHashes
					continue
				}
				if (profileHash != newestProfileHash){
					// There is a newer profile by this same Mate user
					// We don't need to review the current profile, because it is not banned by anyone, and none of its attributes are banned
					// We will review the newer profile instead
					continue
				}

				// We still need to review this attribute, because it is a non-canonical mate attribute
			}
		}

		// The attribute is worthy of review
		// We see if it is less reviewed than our current leastReviewedAttributeHash

		// This will tell us the number of eligible attribute approve advocates
		getAttributeApproveAdvocatesCount := func()int{

			attributeApproveAdvocatesCount, exists := attributeApproveAdvocateCountsMap[attributeIdentifier]
			if (exists == false){
				return 0
			}
			return attributeApproveAdvocatesCount
		}

		// This will tell us the number of eligible attribute ban advocates
		getAttributeBanAdvocatesCount := func()int{

			attributeBanAdvocatesCount, exists := attributeBanAdvocateCountsMap[attributeIdentifier]
			if (exists == false){
				return 0
			}
			return attributeBanAdvocatesCount
		}

		attributeApproveAdvocatesCount := getAttributeApproveAdvocatesCount()
		attributeBanAdvocatesCount := getAttributeBanAdvocatesCount()

		numberOfReviewers := attributeApproveAdvocatesCount + attributeBanAdvocatesCount

		attributeIsSkipped, _, _, err := mySkippedContent.CheckIfAttributeIsSkipped(attributeHash)
		if (err != nil) { return false, [27]byte{}, [28]byte{}, [16]byte{}, err }
		if (attributeIsSkipped == false){
			if (allAttributesAreSkipped == true){

				// This is the first non-skipped attribute we have encountered
				// It may not be the first attribute we have encountered
				// Thus, we must clear any previously found attributes
				leastReviewedAttributeHashFound = false
				leastReviewedAttributeHash = [27]byte{}
				leastReviewedAttributeProfileHash = [28]byte{}
				leastReviewedAttributeIdentityHash = [16]byte{}
				leastReviewedAttributeHashReviewsCount = 0

				allAttributesAreSkipped = false
			}
		} else {
			if (allAttributesAreSkipped == false){
				// We have some non-skipped attributes, so we will reject all skipped attributes
				continue
			}
		}

		if (attributeApproveAdvocatesCount == 0 && attributeIsSkipped == false){
			// This is the highest priority kind of profile attribute
			// It has no approvers, and needs to be reviewed
			// We stop searching and return the attribute
			return true, attributeHash, profileHash, profileAuthor, nil				
		}

		if (leastReviewedAttributeHashFound == false || numberOfReviewers < leastReviewedAttributeHashReviewsCount){
			leastReviewedAttributeHashFound = true
			leastReviewedAttributeHash = attributeHash
			leastReviewedAttributeProfileHash = profileHash
			leastReviewedAttributeIdentityHash = profileAuthor
			leastReviewedAttributeHashReviewsCount = numberOfReviewers
		}
	}

	if (leastReviewedAttributeHashFound == true){
		return true, leastReviewedAttributeHash, leastReviewedAttributeProfileHash, leastReviewedAttributeIdentityHash, nil
	}

	// Nothing left to review
	return false, [27]byte{}, [28]byte{}, [16]byte{}, nil
}





