package daemon

import (
	"strings"
	"time"
)

type elasticRequestLinkState struct {
	grant             ElasticRequestLinkGrant
	sender            elasticRequestLinkSender
	openedAt          time.Time
	lastIdleAt        time.Time
	draining          bool
	currentAssignment string
}

func (manager *ElasticRequestLinkManager) activeCount(machineID string) int {
	if manager == nil {
		return 0
	}
	manager.mu.Lock()
	defer manager.mu.Unlock()
	return manager.activeCountLocked(machineID)
}

func (manager *ElasticRequestLinkManager) activeCountLocked(machineID string) int {
	machineID = strings.TrimSpace(machineID)
	count := 0
	for _, grantState := range manager.grants {
		if grantState.grant.MachineID == machineID && !grantState.consumed {
			count++
		}
	}
	for _, link := range manager.links {
		if link.grant.MachineID == machineID {
			count++
		}
	}
	return count
}

func (manager *ElasticRequestLinkManager) recordRouteDecision(descriptor RemoteEndpointDescriptor, decision RemoteRequestRouteDecision) {
	if manager == nil || manager.config.Metrics == nil {
		return
	}
	manager.config.Metrics.RecordRouteDecision(descriptor.MetricsName, string(descriptor.LinkPolicy), string(decision.Mode), decision.Reason)
}

func (manager *ElasticRequestLinkManager) recordElasticEvent(event string, reason string) {
	if manager == nil || manager.config.Metrics == nil {
		return
	}
	manager.config.Metrics.RecordElasticEvent(event, reason)
}

func (manager *ElasticRequestLinkManager) recordWarmEvent(origin string, profile string, event string, reason string) {
	if manager == nil || manager.config.Metrics == nil {
		return
	}
	manager.config.Metrics.RecordWarmEvent(origin, profile, event, reason)
}

func (manager *ElasticRequestLinkManager) recordPoolLocked() {
	if manager == nil || manager.config.Metrics == nil {
		return
	}
	unconsumedGrants := 0
	for _, grantState := range manager.grants {
		if grantState != nil && !grantState.consumed {
			unconsumedGrants++
		}
	}
	manager.config.Metrics.SetElasticPool("grants", float64(unconsumedGrants))
	manager.config.Metrics.SetElasticPool("links", float64(len(manager.links)))
	manager.config.Metrics.SetElasticPool("assignments", float64(len(manager.assignmentsByID)))
}

// idleLinkLocked only reuses links that are idle, non-draining, bound to the
// same machine and current Session Generation, and whose grant allowlist includes
// the requested endpoint/version.
func (manager *ElasticRequestLinkManager) idleLinkLocked(machineID string, sessionGeneration string, descriptor RemoteEndpointDescriptor) string {
	for linkID := range manager.linksByMachine[strings.TrimSpace(machineID)] {
		link := manager.links[linkID]
		if link == nil || link.currentAssignment != "" || link.draining || link.grant.SessionGeneration != strings.TrimSpace(sessionGeneration) {
			continue
		}
		if grantAllowsEndpoint(link.grant, descriptor.EndpointID, descriptor.Version) {
			return linkID
		}
	}
	return ""
}

func grantAllowsEndpoint(grant ElasticRequestLinkGrant, endpointID string, version int) bool {
	endpointID = strings.TrimSpace(endpointID)
	for _, allowed := range grant.AllowedEndpoints {
		if strings.TrimSpace(allowed.EndpointID) == endpointID && allowed.Version == version {
			return true
		}
	}
	return false
}

func (manager *ElasticRequestLinkManager) removeLinkLocked(linkID string) {
	link := manager.links[linkID]
	if link == nil {
		return
	}
	delete(manager.grants, linkID)
	delete(manager.links, linkID)
	if byMachine := manager.linksByMachine[link.grant.MachineID]; byMachine != nil {
		delete(byMachine, linkID)
		if len(byMachine) == 0 {
			delete(manager.linksByMachine, link.grant.MachineID)
		}
	}
}
