
// mateCriteria provides functions to read/create profile criterias for server requests
// These are used to filter mate profiles from a host using the user's desires
// They have their own encoding

package mateCriteria

import "seekia/internal/desires/mateDesires"
import "seekia/internal/encoding"
import "seekia/internal/profiles/calculatedAttributes"
import "seekia/internal/profiles/readProfiles"

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

import "strings"
import "errors"

//Outputs:
//	-bool: Criteria is valid
//	-bool: Profile fulfills criteria
//	-error
func CheckIfMateProfileFulfillsCriteria(allowParameters bool, profileVersion int, rawProfileMap map[int]messagepack.RawMessage, criteriaBytes []byte)(bool, bool, error){

	profileIsDisabled, _, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(rawProfileMap, "Disabled")
	if (err != nil) { return false, false, err }
	if (profileIsDisabled == true){
		return true, false, nil
	}

	exists, profileType, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(rawProfileMap, "ProfileType")
	if (err != nil) { return false, false, err }
	if (exists == false){
		return false, false, errors.New("CheckIfMateProfileFulfillsCriteria called profile missing ProfileType.")
	}
	if (profileType != "Mate"){
		return false, false, errors.New("CheckIfMateProfileFulfillsCriteria called with non-mate profileType: " + profileType)
	}

	getAnyProfileAttributeFunction, err := calculatedAttributes.GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(profileVersion, rawProfileMap)
	if (err != nil) { return false, false, err }

	criteriaMap := make(map[string]string)

	err = encoding.DecodeMessagePackBytes(false, criteriaBytes, &criteriaMap)
	if (err != nil) { return false, false, err }

	getAnyDesireFunction := func(desireName string)(bool, string, error){

		desireValue, exists := criteriaMap[desireName]
		if (exists == false){
			return false, "", nil
		}
		return true, desireValue, nil
	}

	allDesiresList := mateDesires.GetAllDesiresList(false)

	for _, desireName := range allDesiresList{

		if (desireName == "HasMessagedMe" ||
			desireName == "IHaveMessaged" ||
			desireName == "HasRejectedMe" ||
			desireName == "IsLiked" ||
			desireName == "IsIgnored" ||
			desireName == "IsMyContact" ||
			desireName == "Distance" ||
			desireName == "OffspringProbabilityOfAnyMonogenicDisease"){
			// These desires are not used in criteria
			continue
		}
		if (desireName == "Wealth"){
			// Criteria does not use the Wealth desire. It instead uses the WealthInGold desire
			continue
		}

		desireIsFilterAll := strings.HasSuffix(desireName, "_FilterAll")
		if (desireIsFilterAll == true){
			// We use these to convey if a desire is a FilterAll desire
			continue
		}

		desireIsRequireResponse := strings.HasSuffix(desireName, "_RequireResponse")
		if (desireIsRequireResponse == true){
			// We use these to convey if a desire is a ResponseRequired desire
			continue
		}

		if (allowParameters == false){

			// We do not allow reliance on parameters to determine if the profile passes criteria
			// This will only be false when we are trying to determine if a host is malicious
			// A host's parameters might be different than our own
			// Without this bool, we could falsely believe that a host was malicious
			// The host could serve us a profile that fulfills our criteria when using their parameters,
			//	while our parameters would result in the profile not fulfilling our criteria

			if (desireName == "WealthInGold"){
				// Calculating this requires reliance on the currency exchange rate parameters
				continue
			}
		}

		desireAllowsResponseRequired, attributeNameToCheck := mateDesires.CheckIfDesireAllowsRequireResponse(desireName)
		if (desireAllowsResponseRequired == true){

			// We check to see if the user requires a response

			getRequireResponseBool := func()bool{
				requireResponseStatus, exists := criteriaMap[desireName + "_RequireResponse"]
				if (exists == true && requireResponseStatus == "Yes"){
					return true		
				}
				return false
			}

			requireResponseBool := getRequireResponseBool()

			profileContainsAttribute, _, _, err := getAnyProfileAttributeFunction(attributeNameToCheck)
			if (err != nil) { return false, false, err }
			if (profileContainsAttribute == false){

				// Profile is missing a response

				if (requireResponseBool == true){
					// Criteria requires a response
					return true, false, nil
				}
				// Criteria does not require a response
				continue
			}
		}

		getFilterAllBool := func()bool{
			filterAllStatus, exists := criteriaMap[desireName + "_FilterAll"]
			if (exists == true && filterAllStatus == "Yes"){
				return true		
			}
			return false
		}

		filterAllBool := getFilterAllBool()
		if (filterAllBool == false){
			// User passes this desire
			continue
		}

		anyDesireExists, desireIsValid, userResponseExists, userFulfillsDesire, err := mateDesires.CheckIfMateProfileFulfillsDesire(desireName, getAnyDesireFunction, getAnyProfileAttributeFunction)
		if (err != nil) { return false, false, err }
		if (anyDesireExists == false){
			continue
		}
		if (desireIsValid == false){
			// Criteria is invalid. Requestor must be malicious (or a bug in Seekia).
			return false, false, nil
		}
		if (userResponseExists == false){
			// We already checked for this earlier
			return false, false, errors.New("CheckIfMateProfileFulfillsDesire says user has no response, but we already checked.")
		}
		if (userFulfillsDesire == false){
			return true, false, nil
		}
	}

	// Profile passed all desires

	return true, true, nil
}



