
// readReviews provides functions to read/verify moderator reviews

package readReviews

//TODO: Remove ReviewHash from ReadReview function outputs and create a seperate ReadReviewHash function
// This will prevent hashing from being performed every time we read reviews

import "seekia/internal/allowedText"
import "seekia/internal/cryptography/blake3"
import "seekia/internal/cryptography/edwardsKeys"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/messaging/readMessages"

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

import "bytes"
import "errors"

func VerifyReview(inputReview []byte)(bool, error){

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

	return true, nil
}

func VerifyReviewHash(inputHash [29]byte, reviewTypeProvided bool, expectedReviewType string)(bool, error){

	if (reviewTypeProvided == true){
		if (expectedReviewType != "Identity" && expectedReviewType != "Profile" && expectedReviewType != "Attribute" && expectedReviewType != "Message"){
			return false, errors.New("VerifyReviewHash called with invalid expectedReviewType: " + expectedReviewType)
		}
	}

	reviewType, err := GetReviewTypeFromReviewHash(inputHash)
	if (err != nil){
		return false, nil
	}

	if (reviewTypeProvided == true){
		if (reviewType != expectedReviewType){
			return false, nil
		}
	}

	return true, nil
}

//Outputs:
//	-string: Review type
//	-error
func GetReviewTypeFromReviewHash(inputHash [29]byte)(string, error){

	metadataByte := inputHash[28]

	if (metadataByte == 1){
		return "Identity", nil
	} 
	if (metadataByte == 2){
		return "Profile", nil
	}
	if (metadataByte == 3){
		return "Attribute", nil
	}
	if (metadataByte == 4){
		return "Message", nil
	}

	reviewHashHex := encoding.EncodeBytesToHexString(inputHash[:])

	return "", errors.New("GetReviewTypeFromReviewHash called with invalid reviewHash: " + reviewHashHex)
}

// This function computes the review hash and returns it
//Outputs:
//	-bool: Able to read
//	-[29]byte: Review Hash
//	-int: Review version
//	-byte: Network Type (1 == Mainnet, 2 == Testnet1)
//	-[16]byte: Identity Hash of reviewer (Review author)
//	-int64: Creation time (alleged, can be faked)
//	-string: Review type
//	-[]byte: Reviewed hash
//	-string: Review Verdict
//	-map[string]string: Review Map
//	-error (returns err if there is a bug in the code)
func ReadReviewAndHash(verifyReview bool, inputReview []byte)(bool, [29]byte, int, byte, [16]byte, int64, string, []byte, string, map[string]string, error){

	ableToRead, reviewVersion, reviewNetworkType, reviewAuthor, reviewCreationTime, reviewType, reviewedHash, reviewVerdict, reviewMap, err := ReadReview(verifyReview, inputReview)
	if (err != nil) { return false, [29]byte{}, 0, 0, [16]byte{}, 0, "", nil, "", nil, err }
	if (ableToRead == false){
		return false, [29]byte{}, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	reviewHashWithoutMetadataByte, err := blake3.GetBlake3HashAsBytes(28, inputReview)
	if (err != nil) { return false, [29]byte{}, 0, 0, [16]byte{}, 0, "", nil, "", nil, err }

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

		if (reviewType == "Identity"){

			return 1, nil

		} else if (reviewType == "Profile"){

			return 2, nil

		} else if (reviewType == "Attribute"){

			return 3, nil

		} else if (reviewType == "Message"){

			return 4, nil
		}

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

	reviewHashMetadataByte, err := getReviewHashMetadataByte()
	if (err != nil) { return false, [29]byte{}, 0, 0, [16]byte{}, 0, "", nil, "", nil, err }

	reviewHashSlice := append(reviewHashWithoutMetadataByte, reviewHashMetadataByte)

	reviewHash := [29]byte(reviewHashSlice)

	return true, reviewHash, reviewVersion, reviewNetworkType, reviewAuthor, reviewCreationTime, reviewType, reviewedHash, reviewVerdict, reviewMap, nil
}


//TODO: Encode Verdict as an int to save space

// This function does not compute the review hash, thus it is faster
//Outputs:
//	-bool: Able to read
//	-int: Review version
//	-byte: Network Type (1 == Mainnet, 2 == Testnet1)
//	-[16]byte: Identity Hash of reviewer (Review author)
//	-int64: Creation time (alleged, can be faked)
//	-string: Review type
//	-[]byte: Reviewed hash
//	-string: Review Verdict
//	-map[string]string: Review Map
//	-error (returns err if there is a bug in the code)
func ReadReview(verifyReview bool, inputReview []byte)(bool, int, byte, [16]byte, int64, string, []byte, string, map[string]string, error){

	var reviewSlice []messagepack.RawMessage

	err := messagepack.Unmarshal(inputReview, &reviewSlice)
	if (err != nil) {
		// Invalid review: Malformed messagepack
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	if (len(reviewSlice) != 2){
		// Invalid review: Malformed messagepack
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	signatureMessagepack := reviewSlice[0]
	reviewContentMessagepack := reviewSlice[1]

	reviewSignature, err := encoding.DecodeRawMessagePackTo64ByteArray(signatureMessagepack)
	if (err != nil) {
		// Invalid review: Malformed messagepack
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	reviewContentMap := make(map[int]messagepack.RawMessage)

	err = encoding.DecodeMessagePackBytes(false, reviewContentMessagepack, &reviewContentMap)
	if (err != nil) {
		// Invalid review: Malformed messagepack
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	reviewVersionMessagepack, exists := reviewContentMap[1]
	if (exists == false){
		// Invalid Review: Missing ReviewVersion
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	reviewVersion, err := encoding.DecodeRawMessagePackToInt(reviewVersionMessagepack)
	if (err != nil) {
		// Invalid Review: Invalid review version
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

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

	reviewNetworkTypeMessagepack, exists := reviewContentMap[2]
	if (exists == false){
		// Invalid Review: Missing NetworkType
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	reviewNetworkType, err := encoding.DecodeRawMessagePackToByte(reviewNetworkTypeMessagepack)
	if (err != nil){
		// Invalid Review: Invalid network type messagepack
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	isValid := helpers.VerifyNetworkType(reviewNetworkType)
	if (isValid == false){
		// Invalid Review: Invalid NetworkType
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	authorIdentityKeyMessagepack, exists := reviewContentMap[3]
	if (exists == false){
		// Invalid Review: Missing IdentityKey
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	authorIdentityKey, err := encoding.DecodeRawMessagePackTo32ByteArray(authorIdentityKeyMessagepack)
	if (err != nil) {
		// Invalid Review: Invalid IdentityKey
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	if (verifyReview == true){

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

		isValid := edwardsKeys.VerifySignature(authorIdentityKey, reviewSignature, contentHash)
		if (isValid == false) {
			// Invalid review: Invalid signature
			return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
		}
	}

	authorIdentityHash, err := identity.ConvertIdentityKeyToIdentityHash(authorIdentityKey, "Moderator")
	if (err != nil) { return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, err }

	creationTimeMessagepack, exists := reviewContentMap[4]
	if (exists == false){
		// Invalid Review: Missing CreationTime
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	creationTime, err := encoding.DecodeRawMessagePackToInt64(creationTimeMessagepack)
	if (err != nil) {
		// Invalid Review: Invalid CreationTime
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	reviewedHashMessagepack, exists := reviewContentMap[5]
	if (exists == false){
		// Invalid Review: Missing ReviewedHash
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	reviewedHash, err := encoding.DecodeRawMessagePackToBytes(reviewedHashMessagepack)
	if (err != nil) {
		// Invalid Review: Invalid ReviewedHash
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	reviewType, err := helpers.GetReviewedTypeFromReviewedHash(reviewedHash)
	if (err != nil){
		// Invalid Review: Invalid ReviewedHash
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	verdictMessagepack, exists := reviewContentMap[6]
	if (exists == false){
		// Invalid Review: Missing Verdict
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	reviewVerdict, err := encoding.DecodeRawMessagePackToString(verdictMessagepack)
	if (err != nil) {
		// Invalid Review: Invalid Verdict
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
	}

	// These fields are not included within all reviews	
	var reason string
	var messageCipherKeyBytes []byte

	reasonMessagepack, exists := reviewContentMap[7]
	if (exists == true){

		err = messagepack.Unmarshal(reasonMessagepack, &reason)
		if (err != nil) {
			// Invalid Review: Invalid Reason
			return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
		}
	}

	if (reviewType == "Message"){

		messageCipherKeyMessagepack, exists := reviewContentMap[8]
		if (exists == false){
			// Invalid Review: Message review missing MessageCipherKey
			return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
		}

		err = messagepack.Unmarshal(messageCipherKeyMessagepack, &messageCipherKeyBytes)
		if (err != nil) {
			// Invalid Review: Invalid MessageCipherKey
			return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
		}
	}

	if (verifyReview == true){

		isValid := helpers.VerifyCreationTime(creationTime)
		if (isValid == false){
			// Invalid review: Review contains contains invalid CreationTime
			return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
		}

		if (reviewVerdict != "Ban" && reviewVerdict != "Approve" && reviewVerdict != "None"){
			// Invalid review: Review contains invalid Verdict
			return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
		}

		if (reviewType == "Identity" && reviewVerdict == "Approve"){
			// Invalid review: Review contains approve verdict for identity review
			return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
		}

		if (reviewType == "Identity"){

			areEqual := bytes.Equal(reviewedHash, authorIdentityHash[:])
			if (areEqual == true){
				// Invalid review: Author cannot ban themselves.
				return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
			}

			//TODO: Verify ErrantProfiles, ErrantMessages, ErrantReviews, and ErrantAttributes

		} else if (reviewType == "Message"){

			if (len(messageCipherKeyBytes) != 32){
				// Invalid review: Message review contains invalid messageCipherKey
				return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
			}
		}

		if (reason != "") {

			if (len(reason) > 200) {
				// Invalid review: ReviewMap contains invalid reason
				return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
			}

			isAllowed := allowedText.VerifyStringIsAllowed(reason)
			if (isAllowed == false){
				// Invalid review: ReviewMap contains invalid reason
				return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, nil
			}
		}
	}

	reviewNetworkTypeString := helpers.ConvertByteToString(reviewNetworkType)

	authorIdentityKeyHex := encoding.EncodeBytesToHexString(authorIdentityKey[:])

	creationTimeString := helpers.ConvertInt64ToString(creationTime)

	reviewedHashString, err := helpers.EncodeReviewedHashBytesToString(reviewedHash)
	if (err != nil){
		return false, 0, 0, [16]byte{}, 0, "", nil, "", nil, errors.New("GetReviewedTypeFromReviewedHash not verifying reviewedHash.")
	}

	reviewMap := map[string]string{
		"ReviewVersion": "1",
		"NetworkType": reviewNetworkTypeString,
		"IdentityKey": authorIdentityKeyHex,
		"CreationTime": creationTimeString,
		"ReviewedHash": reviewedHashString,
		"Verdict": reviewVerdict,
	}

	if (reason != ""){
		reviewMap["Reason"] = reason
	}
	if (reviewType == "Message"){

		messageCipherKey := encoding.EncodeBytesToHexString(messageCipherKeyBytes)

		reviewMap["MessageCipherKey"] = messageCipherKey
	}

	return true, 1, reviewNetworkType, authorIdentityHash, creationTime, reviewType, reviewedHash, reviewVerdict, reviewMap, nil
}


// This struct is used to represent a review and its hash
// This is useful, because we do not need to hash each review to retrieve its hash
type ReviewWithHash struct{
	ReviewHash [29]byte
	ReviewBytes []byte
}

//Outputs:
//	-map[[16]byte]int64: Ban advocates map (identity hash -> Time of ban)
//	-error
func GetIdentityBanAdvocatesMapFromReviewsList(reviewsList []ReviewWithHash, identityHash [16]byte, networkType byte)(map[[16]byte]int64, error){

	checkIfReviewIsValid := func(_ map[string]string)(bool, error){
		return true, nil
	}

	approveAdvocatesMap, banAdvocatesMap, err := getModeratorNewestVerdictMapsFromReviewsList(reviewsList, "Identity", identityHash[:], networkType, checkIfReviewIsValid)
	if (err != nil) { return nil, err }
	if (len(approveAdvocatesMap) != 0){
		return nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList returning non-empty approve advocates map for identity.")
	}

	return banAdvocatesMap, nil
}

// 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 GetMessageVerdictMapsFromReviewsList(reviewsList []ReviewWithHash, messageHash [26]byte, messageNetworkType byte, messageCipherKeyHash [25]byte)(map[[16]byte]int64, map[[16]byte]int64, error){

	// We must check to make sure review has a valid messageCipherKey
	// This will function as a proof that the reviewer has seen the message
	// If the cipher key hash is valid but the key cannot decrypt the message, we know the reviewer and message author are both malicious 
	// We can detect these kinds of reviews and automatically ban the authors

	verifyReviewIsValidFunction := func(reviewMap map[string]string)(bool, error){

		isValid, err := VerifyMessageReviewCipherKey(reviewMap, messageCipherKeyHash)

		return isValid, err
	}

	approveAdvocatesMap, banAdvocatesMap, err := getModeratorNewestVerdictMapsFromReviewsList(reviewsList, "Message", messageHash[:], messageNetworkType, verifyReviewIsValidFunction)
	if (err != nil) { return nil, nil, err }

	return approveAdvocatesMap, banAdvocatesMap, nil
} 


// This function is used to verify that message reviews are valid
func VerifyMessageReviewCipherKey(reviewMap map[string]string, messageCipherKeyHash [25]byte)(bool, error){

	reviewMessageCipherKey, exists := reviewMap["MessageCipherKey"]
	if (exists == false) {
		return false, errors.New("VerifyMessageReviewCipherKey called with message review missing MessageCipherKey")
	}

	reviewMessageCipherKeyArray, err := readMessages.ReadMessageCipherKeyHex(reviewMessageCipherKey)
	if (err != nil){
		return false, errors.New("VerifyMessageReviewCipherKey called with message review containing invalid MessageCipherKey: " + reviewMessageCipherKey + ". Reason: " + err.Error())
	}

	reviewCipherKeyHash, err := readMessages.ConvertMessageCipherKeyToCipherKeyHash(reviewMessageCipherKeyArray)
	if (err != nil){ return false, err }

	if (reviewCipherKeyHash != messageCipherKeyHash){
		// Reviewer must be malicious. Skip review.
		return false, nil
	}

	return true, nil
}


//Outputs:
//	-map[[16]byte]int64: Approve advocates map (identity hash -> Time of approve)
//	-map[[16]byte]int64: Ban advocates map (identity hash -> Time of ban)
//	-error
func GetProfileVerdictMapsFromReviewsList(reviewsList []ReviewWithHash, profileHash [28]byte, profileNetworkType byte)(map[[16]byte]int64, map[[16]byte]int64, error){

	verifyReviewIsValid := func(_ map[string]string)(bool, error){
		return true, nil
	}

	approveAdvocatesMap, banAdvocatesMap, err := getModeratorNewestVerdictMapsFromReviewsList(reviewsList, "Profile", profileHash[:], profileNetworkType, verifyReviewIsValid)
	if (err != nil) { return nil, nil, err }

	return approveAdvocatesMap, banAdvocatesMap, nil
}

//Outputs:
//	-map[[16]byte]int64: Approve advocates map (identity hash -> Time of approval)
//	-map[[16]byte]int64: Ban advocates map (identity hash -> Time of ban)
//	-error
func GetProfileAttributeVerdictMapsFromReviewsList(reviewsList []ReviewWithHash, attributeHash [27]byte, attributeNetworkType byte)(map[[16]byte]int64, map[[16]byte]int64, error){

	verifyReviewIsValid := func(_ map[string]string)(bool, error){
		return true, nil
	}

	approveAdvocatesMap, banAdvocatesMap, err := getModeratorNewestVerdictMapsFromReviewsList(reviewsList, "Attribute", attributeHash[:], attributeNetworkType, verifyReviewIsValid)
	if (err != nil) { return nil, nil, err }

	return approveAdvocatesMap, banAdvocatesMap, nil
}


// This function takes a list of reviews and a reviewedHash and returns maps for each moderator's newest verdict ("Approve"/"Ban")
// This requires keeping track of the newest review from each moderator. Moderators can undo reviews by using the None verdict.
//Outputs:
//	-map[[16]byte]int64: Approve advocates map (identity hash -> Time approve review was submitted)
//	-map[[16]byte]int64: Ban advocates map (identity hash -> Time ban review was submitted)
//	-error
func getModeratorNewestVerdictMapsFromReviewsList(reviewsList []ReviewWithHash, reviewType string, reviewedHash []byte, networkType byte, verifyReviewIsValid func(map[string]string)(bool, error))(map[[16]byte]int64, map[[16]byte]int64, error){

	if (reviewType != "Identity" && reviewType != "Profile" && reviewType != "Attribute" && reviewType != "Message"){
		return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with invalid ReviewType: " + reviewType)
	}

	reviewedType, err := helpers.GetReviewedTypeFromReviewedHash(reviewedHash)
	if (err != nil){
		reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
		return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with invalid reviewedHash: " + reviewedHashHex)
	}
	if (reviewedType != reviewType){
		return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with reviewType that does not match reviewedHash")
	}

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

	// This stores info about a review
	type ReviewInfoObject struct{
		ReviewHash [29]byte
		CreationTime int64
		Verdict string
	}

	// This map stores info about each reviewer's newest review for the provided reviewedHash
	// Map Structure: Reviewer Identity Hash -> Newest Review Info Object
	newestReviewsMap := make(map[[16]byte]ReviewInfoObject)

	for _, reviewWithHash := range reviewsList{

		reviewHash := reviewWithHash.ReviewHash
		reviewBytes := reviewWithHash.ReviewBytes

		ableToRead, _, reviewNetworkType, reviewAuthor, reviewCreationTime, currentReviewType, currentReviewedHash, reviewVerdict, reviewMap, err := ReadReview(false, reviewBytes)
		if (err != nil) { return nil, nil, err }
		if (ableToRead == false){
			return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with invalid review.")
		}
		if (reviewNetworkType != networkType){
			continue
		}

		if (currentReviewType != reviewType){
			continue
		}

		areEqual := bytes.Equal(currentReviewedHash, reviewedHash)
		if (areEqual == false){
			continue
		}

		isValid, err := verifyReviewIsValid(reviewMap)
		if (err != nil) { return nil, nil, err }
		if (isValid == false){
			continue
		}

		existingNewestReviewInfoObject, exists := newestReviewsMap[reviewAuthor]
		if (exists == true){

			existingNewestReviewCreationTime := existingNewestReviewInfoObject.CreationTime

			if (existingNewestReviewCreationTime == reviewCreationTime){
				// The reviewer must be malicious, or their computer clock was skewed somehow
				// We compare the review hashes to see which review to use
				// This is necessary so that a moderator's newest review will always be calculated the same way by all Seekia clients.

				existingNewestReviewHash := existingNewestReviewInfoObject.ReviewHash

				compareValue := bytes.Compare(reviewHash[:], existingNewestReviewHash[:])
				if (compareValue == 0){
					// This should not happen, because this function should be called with a list of unique reviews
					return nil, nil, errors.New("getModeratorNewestVerdictMapsFromReviewsList called with reviewsList containing duplicate review.")
				}

				if (compareValue == -1){
					continue
				}

			} else if (existingNewestReviewCreationTime > reviewCreationTime){
				// This review was overwritten/replaced by a newer review
				continue
			}
		}

		newNewestReviewInfoObject := ReviewInfoObject{
			ReviewHash: reviewHash,
			CreationTime: reviewCreationTime,
			Verdict: reviewVerdict,
		}

		newestReviewsMap[reviewAuthor] = newNewestReviewInfoObject
	}

	// Map Structure: Identity hash -> Time of approve review
	approveAdvocatesMap := make(map[[16]byte]int64)

	// Map Structure: Identity hash -> Time of ban review
	banAdvocatesMap := make(map[[16]byte]int64)

	for reviewerIdentityHash, reviewerNewestReviewInfoObject := range newestReviewsMap{

		newestReviewVerdict := reviewerNewestReviewInfoObject.Verdict
		reviewCreationTime := reviewerNewestReviewInfoObject.CreationTime

		if (newestReviewVerdict == "Approve"){

			approveAdvocatesMap[reviewerIdentityHash] = reviewCreationTime

			continue

		} else if (newestReviewVerdict == "Ban"){

			banAdvocatesMap[reviewerIdentityHash] = reviewCreationTime

			continue

		} else if (newestReviewVerdict == "None"){

			continue
		}

		return nil, nil, errors.New("ReadReview returning invalid verdict: " + newestReviewVerdict)
	}

	return approveAdvocatesMap, banAdvocatesMap, nil
}


//Outputs:
//	-bool: Review exists (this means the moderator has banned the identity)
//	-[]byte: Newest Review bytes
//	-map[string]string: Newest Review map
//	-error
func GetModeratorNewestIdentityReviewFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, reviewedIdentityHash [16]byte, networkType byte)(bool, []byte, map[string]string, error){

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

	checkIfReviewIsValid := func(_ map[string]string)(bool, error){
		return true, nil
	}

	reviewExists, reviewBytes, reviewMap, _, _, err := getNewestModeratorReviewFromReviewsList(inputReviewsList, moderatorIdentityHash, "Identity", reviewedIdentityHash[:], networkType, checkIfReviewIsValid)

	return reviewExists, reviewBytes, reviewMap, err
}

//Outputs:
//	-bool: Review exists
//	-[]byte: Newest review bytes
//	-map[string]string: Newest review map
//	-string: Review verdict
//	-int64: Time of review
//	-error
func GetModeratorNewestProfileReviewFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, profileHash [28]byte, profileNetworkType byte)(bool, []byte, map[string]string, string, int64, error){

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

	verifyReviewIsValidFunction := func(_ map[string]string)(bool, error){
		return true, nil
	}

	reviewExists, reviewBytes, reviewMap, reviewVerdict, reviewCreationTime, err := getNewestModeratorReviewFromReviewsList(inputReviewsList, moderatorIdentityHash, "Profile", profileHash[:], profileNetworkType, verifyReviewIsValidFunction)

	return reviewExists, reviewBytes, reviewMap, reviewVerdict, reviewCreationTime, err
}

//Outputs:
//	-bool: Review exists
//	-[]byte: Newest review bytes
//	-map[string]string: Newest review map
//	-string: Newest Review verdict
//	-int64: Time of review
//	-error
func GetModeratorNewestProfileAttributeReviewFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, attributeHash [27]byte, attributeNetworkType byte)(bool, []byte, map[string]string, string, int64, error){

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

	verifyReviewIsValidFunction := func(_ map[string]string)(bool, error){
		return true, nil
	}

	reviewExists, reviewBytes, reviewMap, reviewVerdict, verdictTime, err := getNewestModeratorReviewFromReviewsList(inputReviewsList, moderatorIdentityHash, "Attribute", attributeHash[:], attributeNetworkType, verifyReviewIsValidFunction)

	return reviewExists, reviewBytes, reviewMap, reviewVerdict, verdictTime, err
}

//Outputs:
//	-bool: Review exists
//	-[]byte: Newest review bytes
//	-map[string]string: Newest review map
//	-string: Review verdict
//	-error
func GetModeratorNewestMessageReviewFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, messageHash [26]byte, messageNetworkType byte, messageCipherKeyHash [25]byte)(bool, []byte, map[string]string, string, error){

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

	verifyReviewIsValidFunction := func(reviewMap map[string]string)(bool, error){

		isValid, err := VerifyMessageReviewCipherKey(reviewMap, messageCipherKeyHash)

		return isValid, err
	}

	reviewExists, reviewBytes, reviewMap, reviewVerdict, _, err := getNewestModeratorReviewFromReviewsList(inputReviewsList, moderatorIdentityHash, "Message", messageHash[:], messageNetworkType, verifyReviewIsValidFunction)

	return reviewExists, reviewBytes, reviewMap, reviewVerdict, err
}

// This function takes input of a list of reviews
// Returns newest review for a specified moderator, reviewedHash and reviewType
// Will omit review with "None" verdict
// Inputs:
//	-[][]byte: List of reviews created by moderator
//	-[16]byte: Identity hash of moderator
//	-byte: Network type
//	-string: Review Type
//	-[]byte: Reviewed hash
//	-func(map[string]string)(bool, error): Takes review map as input, returns if review is valid
//Outputs:
//	-bool: Review exists (will be false if newest review is a None verdict)
//	-[]byte: Newest review bytes
//	-map[string]string: Newest review map
//	-string: Newest review verdict
//	-int64: Newest review creation time
//	-error
func getNewestModeratorReviewFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, reviewType string, reviewedHash []byte, networkType byte, verifyReviewIsValid func(map[string]string)(bool, error))(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("getNewestModeratorReviewFromReviewsList called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex)
	}

	if (reviewType != "Identity" && reviewType != "Profile" && reviewType != "Attribute" && reviewType != "Message"){
		return false, nil, nil, "", 0, errors.New("getNewestModeratorReviewFromReviewsList called with invalid reviewType: " + reviewType)
	}

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

	anyReviewFound := false
	newestReviewHash := [29]byte{}
	newestReviewBytes := make([]byte, 0)
	newestReviewMap := make(map[string]string)
	newestReviewCreationTime := int64(0)
	newestReviewVerdict := ""

	for _, reviewWithHash := range inputReviewsList{

		reviewHash := reviewWithHash.ReviewHash
		reviewBytes := reviewWithHash.ReviewBytes

		ableToRead, _, reviewNetworkType, reviewAuthor, reviewCreationTime, currentReviewType, currentReviewedHash, reviewVerdict, reviewMap, err := ReadReview(false, reviewBytes)
		if (err != nil) { return false, nil, nil, "", 0, err }
		if (ableToRead == false){
			return false, nil, nil, "", 0, errors.New("getNewestModeratorReviewFromReviewsList called with invalid review")
		}
		if (reviewNetworkType != networkType){
			continue
		}

		if (reviewAuthor != moderatorIdentityHash){
			continue
		}

		if (currentReviewType != reviewType) {
			continue
		}

		areEqual := bytes.Equal(currentReviewedHash, reviewedHash)
		if (areEqual == false){
			continue
		}

		reviewIsValid, err := verifyReviewIsValid(reviewMap)
		if (err != nil){ return false, nil, nil, "", 0, err }
		if (reviewIsValid == false){
			continue
		}

		if (anyReviewFound == false || reviewCreationTime >= newestReviewCreationTime){

			if (reviewCreationTime == newestReviewCreationTime){
				// The reviewer must be malicious, or their computer clock was skewed somehow
				// We compare the review hashes to see which review to use
				// This is necessary so that a moderator's newest review will always be calculated the same way by all Seekia clients.

				compareValue := bytes.Compare(reviewHash[:], newestReviewHash[:])
				if (compareValue == 0){
					// This should not happen, because this function should be called with a list of unique reviews
					return false, nil, nil, "", 0, errors.New("getNewestModeratorReviewFromReviewsList called with reviewsList containing duplicate review.")
				}

				if (compareValue == -1){
					continue
				}
			}

			anyReviewFound = true
			newestReviewHash = reviewHash
			newestReviewBytes = reviewBytes
			newestReviewMap = reviewMap
			newestReviewCreationTime = reviewCreationTime
			newestReviewVerdict = reviewVerdict
		}
	}

	if (anyReviewFound == false){
		return false, nil, nil, "", 0, nil
	}
	if (newestReviewVerdict == "None"){
		return false, nil, nil, "", 0, nil
	}

	return true, newestReviewBytes, newestReviewMap, newestReviewVerdict, newestReviewCreationTime, nil
}


// This function returns a map of reviews created by a particular moderator, of a specific reviewType (if provided)
// The map stores the moderator's newest review for each piece of content
// The function will omit reviews whose newest verdict is "None"
// The function will return error if any review is not created by moderator
// The function does not verify the reviews
//Outputs:
//	-map[string][]byte: Reviewed Hash (bytes encoded as string) -> Newest Review bytes
//	-error
func GetNewestModeratorReviewsMapFromReviewsList(inputReviewsList []ReviewWithHash, moderatorIdentityHash [16]byte, networkType byte, reviewTypeProvided bool, reviewType string)(map[string][]byte, error){

	isValid, err := identity.VerifyIdentityHash(moderatorIdentityHash, true, "Moderator")
	if (err != nil) { return nil, err }
	if (isValid == false){

		moderatorIdentityHashHex := encoding.EncodeBytesToHexString(moderatorIdentityHash[:])

		return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList called with invalid moderatorIdentityHash: " + moderatorIdentityHashHex)
	}

	isValid = helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)

		return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList called with invalid networkType: " + networkTypeString)
	}

	if (reviewTypeProvided == true){
		if (reviewType != "Identity" && reviewType != "Profile" && reviewType != "Attribute" && reviewType != "Message"){
			return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList called with invalid reviewType: " + reviewType)
		}
	}

	type ReviewInfoObject struct{
		ReviewHash [29]byte
		Verdict string
		CreationTime int64
		ReviewBytes []byte
	}

	// This map stores the newest review info for each reviewedHash
	// Map Structure: Reviewed Hash -> Review info object of newest review
	newestReviewInfoObjectsMap := make(map[string]ReviewInfoObject)

	for _, reviewWithHash := range inputReviewsList{

		reviewHash := reviewWithHash.ReviewHash
		reviewBytes := reviewWithHash.ReviewBytes

		ableToRead, _, reviewNetworkType, reviewAuthor, reviewCreationTime, currentReviewType, reviewedHash, reviewVerdict, _, err := ReadReview(false, reviewBytes)
		if (err != nil) { return nil, err }
		if (ableToRead == false){
			return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList called with invalid review")
		}
		if (reviewNetworkType != networkType){
			continue
		}

		if (reviewAuthor != moderatorIdentityHash){
			return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList receiving review by different reviewer")
		}

		if (reviewTypeProvided == true && currentReviewType != reviewType){
			continue
		}
	
		existingNewestReviewInfoObject, exists := newestReviewInfoObjectsMap[string(reviewedHash)]
		if (exists == true){

			existingCreationTime := existingNewestReviewInfoObject.CreationTime
			if (exists == false){
				return nil, errors.New("reviewCreationTimesMap missing reviewHash entry")
			}

			if (existingCreationTime == reviewCreationTime){
				// The reviewer must be malicious, or their computer clock was skewed somehow
				// We compare the review hashes to see which review to use
				// This is necessary so that a moderator's newest review will always be calculated the same way by all Seekia clients.

				existingNewestReviewHash := existingNewestReviewInfoObject.ReviewHash

				compareValue := bytes.Compare(reviewHash[:], existingNewestReviewHash[:])
				if (compareValue == 0){
					// This should not happen, because this function should be called with a list of unique reviews
					return nil, errors.New("GetNewestModeratorReviewsMapFromReviewsList called with reviewsList containing duplicate review.")
				}

				if (compareValue == -1){
					continue
				}

			} else if (existingCreationTime > reviewCreationTime){
				// The user has replaced their review with a newer one
				continue
			}
		}

		newNewestReviewInfoObject := ReviewInfoObject{
			ReviewHash: reviewHash,
			Verdict: reviewVerdict,
			CreationTime: reviewCreationTime,
			ReviewBytes: reviewBytes,
		}

		newestReviewInfoObjectsMap[string(reviewedHash)] = newNewestReviewInfoObject
	}

	//Map Structure: Reviewed Hash -> Newest Review bytes
	newestReviewsMap := make(map[string][]byte)

	for reviewedHash, reviewInfoObject := range newestReviewInfoObjectsMap{

		currentVerdict := reviewInfoObject.Verdict

		if (currentVerdict == "None"){
			continue
		}

		reviewBytes := reviewInfoObject.ReviewBytes

		newestReviewsMap[reviewedHash] = reviewBytes
	}

	return newestReviewsMap, nil
}



