
// eligibleHosts provides a function to get a list of eligible Seekia hosts
// Eligible = not banned, not unreachable, not malicious, not disabled, funded, not blocked, and whose profiles are not expired

package eligibleHosts

// The list of hosts we return might not fulfill some of the eligibility requirements if not enough hosts are available who do.
// For example, if a user has not opened their Seekia client since the host profile expiration time has passed,
//		all of the stored host profiles will be expired.
// Thus, expired hosts will be included in the eligible hosts list when not enough hosts are available.
// This is necessary to be able to download newer unexpired host profiles

// If there are insufficient eligible-but-expired hosts, the function will return at least some blocked/banned/unreachable/malicious hosts.
// This is because a malicious moderator could ban all hosts, or we could have falsely marked hosts as being unreachable

// TODO: The network entry hosts, which are coded into the client and provided in the parameters
// These hosts will always be eligible
//		They are the hosts that the client initially connects to, to discover other hosts

//TODO: Store eligible hosts list in memory and refresh automatically using backgroundJobs?

import "seekia/internal/helpers"
import "seekia/internal/myBlockedUsers"
import "seekia/internal/network/enabledHosts"
import "seekia/internal/network/unreachableHosts"
import "seekia/internal/network/maliciousHosts"
import "seekia/internal/network/getFundedStatuses"
import "seekia/internal/profiles/viewableProfiles"
import "seekia/internal/parameters/getParameters"

import "time"
import "slices"
import "errors"

func GetEligibleHostsList(networkType byte)([][16]byte, error){

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

	enabledHostsList, err := enabledHosts.GetEnabledHostsList(true, networkType)
	if (err != nil) { return nil, err }

	helpers.RandomizeListOrder(enabledHostsList)

	// We start by only removing hosts who are blocked
	unblockedHostsList := make([][16]byte, 0)

	for _, hostIdentityHash := range enabledHostsList{

		hostIsBlocked, _, _, _, err := myBlockedUsers.CheckIfUserIsBlocked(hostIdentityHash)
		if (err != nil) { return nil, err }
		if (hostIsBlocked == true){
			continue
		}

		unblockedHostsList = append(unblockedHostsList, hostIdentityHash)
	}

	if (len(unblockedHostsList) < 10){
		// We don't have enough hosts to be any more selective
		// We will never serve a blocked host, even if no hosts are available.
		// This is because a blocked host will always be manually blocked by the user

		return unblockedHostsList, nil
	}

	//Outputs:
	//	-[]string: New list
	//	-error
	fillHostsListWithLessDesireableHosts := func(inputList [][16]byte, lessDesireableHostsList [][16]byte)([][16]byte, error){

		for _, hostIdentityHash := range lessDesireableHostsList{

			alreadyAdded := slices.Contains(inputList, hostIdentityHash)
			if (alreadyAdded == true){
				continue
			}

			inputList = append(inputList, hostIdentityHash)

			if (len(inputList) >= 10){
				return inputList, nil
			}
		}

		// This is only reached if not enough hosts were available in the lessDesireableHostsList
		return nil, errors.New("fillHostsListWithLessDesireableHosts called with lessDesireableHostsList with insufficient hosts.")
	}

	nonmaliciousHostsList := make([][16]byte, 0)

	for _, hostIdentityHash := range unblockedHostsList{

		hostIsMalicious, err := maliciousHosts.CheckIfHostIsMalicious(hostIdentityHash)
		if (err != nil) { return nil, err }
		if (hostIsMalicious == true){
			continue
		}

		nonmaliciousHostsList = append(nonmaliciousHostsList, hostIdentityHash)
	}

	if (len(nonmaliciousHostsList) < 10){

		// We don't have enough hosts to be any more selective

		resultList, err := fillHostsListWithLessDesireableHosts(nonmaliciousHostsList, unblockedHostsList)
		if (err != nil) { return nil, err }

		return resultList, nil
	}

	reachableHostsList := make([][16]byte, 0)

	for _, hostIdentityHash := range nonmaliciousHostsList{

		hostIsUnreachableClearnet, err := unreachableHosts.CheckIfHostIsUnreachable(hostIdentityHash, networkType, "Clearnet")
		if (err != nil) { return nil, err }
	
		hostIsUnreachableTor, err := unreachableHosts.CheckIfHostIsUnreachable(hostIdentityHash, networkType, "Tor")
		if (err != nil) { return nil, err }

		if (hostIsUnreachableClearnet == true && hostIsUnreachableTor == true){
			continue
		}

		reachableHostsList = append(reachableHostsList, hostIdentityHash)
	}

	if (len(reachableHostsList) < 10){

		resultList, err := fillHostsListWithLessDesireableHosts(reachableHostsList, nonmaliciousHostsList)
		if (err != nil) { return nil, err }

		return resultList, nil
	}

	fundedHostsList := make([][16]byte, 0)

	for _, hostIdentityHash := range reachableHostsList{

		statusIsKnown, identityIsFunded, _, err := getFundedStatuses.GetIdentityIsFundedStatus(hostIdentityHash, networkType)
		if (err != nil) { return nil, err }
		if (statusIsKnown == true && identityIsFunded == false){
			continue
		}

		fundedHostsList = append(fundedHostsList, hostIdentityHash)
	}

	if (len(fundedHostsList) < 10){

		resultList, err := fillHostsListWithLessDesireableHosts(fundedHostsList, reachableHostsList)
		if (err != nil) { return nil, err }

		return resultList, nil
	}

	viewableHostsList := make([][16]byte, 0)

	// Map structure: Host identity hash -> Newest viewable profile creation time
	hostCreationTimesMap := make(map[[16]byte]int64)

	for _, hostIdentityHash := range fundedHostsList{

		// Now we check to see if identity is not banned, and has a profile that is viewable
		// If the consensus status is unknown, we will allow the user to request from the host
		// This is necessary, because otherwise new users and hosts would not be able to connect to any hosts, because
		//		they would not know the sticky consensus status of any hosts

		//TODO: Check if host is a network entry host, in which case, they cannot be banned

		exists, _, _, _, profileCreationTime, _, err := viewableProfiles.GetNewestViewableUserProfile(hostIdentityHash, networkType, true, true, true)
		if (err != nil) { return nil, err }
		if (exists == false){
			continue
		}

		hostCreationTimesMap[hostIdentityHash] = profileCreationTime

		viewableHostsList = append(viewableHostsList, hostIdentityHash)
	}

	if (len(viewableHostsList) < 10){

		resultList, err := fillHostsListWithLessDesireableHosts(viewableHostsList, fundedHostsList)
		if (err != nil) { return nil, err }

		return resultList, nil
	}

	// Now we check for hosts whose profiles are active
	// If the user has not turned Seekia on and downloaded profiles since the host inactivity expiration duration,
	//		all host profiles will be expired

	unexpiredHostsList := make([][16]byte, 0)

	for _, hostIdentityHash := range viewableHostsList{

		hostProfileCreationTime, exists := hostCreationTimesMap[hostIdentityHash]
		if (exists == false){
			return nil, errors.New("hostCreationTimesMap missing host identity hash.")
		}

		_, maximumExistenceDuration, err := getParameters.GetHostProfileMaximumExistenceDuration(networkType)
		if (err != nil) { return nil, err }

		currentTime := time.Now().Unix()
	
		profileAge := currentTime - hostProfileCreationTime

		if (profileAge > maximumExistenceDuration){
			// Host's profile has expired
			continue
		}

		unexpiredHostsList = append(unexpiredHostsList, hostIdentityHash)
	}

	if (len(unexpiredHostsList) < 10){

		resultList, err := fillHostsListWithLessDesireableHosts(unexpiredHostsList, viewableHostsList)
		if (err != nil) { return nil, err }

		return resultList, nil
	}

	return unexpiredHostsList, nil
}



