package executionpool

import (
	"errors"
	"math"
	"sort"
	"strings"
	"time"
)

const (
	defaultHealthMaxAge      = 45 * time.Second
	defaultMinimumDwell      = 2 * time.Minute
	defaultSwitchImprovement = 0.20
	defaultBetterWindowCount = 3
)

var ErrNoEligiblePool = errors.New("no eligible execution pool")

type SelectionCandidate struct {
	Pool              ExecutionPool
	Health            RouteHealth
	Capacity          CapacitySnapshot
	CapabilityMatched bool
	BetterWindows     int
}

type SelectionRequest struct {
	Candidates       []SelectionCandidate
	CurrentPoolID    string
	CurrentPoolSince time.Time
	Now              time.Time
}

type Decision struct {
	PoolID     string
	Score      float64
	Reason     string
	Components map[string]float64
	Rejected   map[string]string
}

type Selector struct {
	HealthMaxAge      time.Duration
	MinimumDwell      time.Duration
	SwitchImprovement float64
	BetterWindows     int
}

func NewSelector() Selector {
	return Selector{
		HealthMaxAge: defaultHealthMaxAge, MinimumDwell: defaultMinimumDwell,
		SwitchImprovement: defaultSwitchImprovement, BetterWindows: defaultBetterWindowCount,
	}
}

type scoredCandidate struct {
	candidate  SelectionCandidate
	score      float64
	components map[string]float64
}

func (s Selector) Select(request SelectionRequest) (Decision, error) {
	now := request.Now
	if now.IsZero() {
		now = time.Now()
	}
	if s.HealthMaxAge <= 0 {
		s.HealthMaxAge = defaultHealthMaxAge
	}
	if s.MinimumDwell <= 0 {
		s.MinimumDwell = defaultMinimumDwell
	}
	if s.SwitchImprovement <= 0 {
		s.SwitchImprovement = defaultSwitchImprovement
	}
	if s.BetterWindows <= 0 {
		s.BetterWindows = defaultBetterWindowCount
	}
	rejected := make(map[string]string)
	scored := make([]scoredCandidate, 0, len(request.Candidates))
	for _, candidate := range request.Candidates {
		if reason := rejectionReason(candidate, now, s.HealthMaxAge); reason != "" {
			rejected[candidate.Pool.ID] = reason
			continue
		}
		network := routeQuality(candidate.Health)
		queue := queueQuality(candidate.Capacity)
		resources := clamp01(candidate.Capacity.ResourceHeadroom)
		stability := clamp01(candidate.Capacity.Stability)
		components := map[string]float64{
			"network": network, "queue": queue, "resources": resources, "stability": stability,
		}
		scored = append(scored, scoredCandidate{
			candidate:  candidate,
			score:      0.55*network + 0.25*queue + 0.15*resources + 0.05*stability,
			components: components,
		})
	}
	if len(scored) == 0 {
		return Decision{Rejected: rejected}, ErrNoEligiblePool
	}
	sort.SliceStable(scored, func(i, j int) bool {
		if math.Abs(scored[i].score-scored[j].score) > 0.000001 {
			return scored[i].score > scored[j].score
		}
		return scored[i].candidate.Pool.ID < scored[j].candidate.Pool.ID
	})
	selected := scored[0]
	if currentIndex := indexOfPool(scored, strings.TrimSpace(request.CurrentPoolID)); currentIndex >= 0 && currentIndex != 0 {
		current := scored[currentIndex]
		withinDwell := !request.CurrentPoolSince.IsZero() && now.Sub(request.CurrentPoolSince) < s.MinimumDwell
		improvement := relativeImprovement(selected.score, current.score)
		if withinDwell || improvement < s.SwitchImprovement || selected.candidate.BetterWindows < s.BetterWindows {
			selected = current
		}
	}
	return Decision{
		PoolID:     selected.candidate.Pool.ID,
		Score:      selected.score,
		Reason:     "network_quality_then_safe_capacity",
		Components: selected.components,
		Rejected:   rejected,
	}, nil
}

func rejectionReason(candidate SelectionCandidate, now time.Time, maxAge time.Duration) string {
	if strings.TrimSpace(candidate.Pool.ID) == "" {
		return "invalid_pool"
	}
	if candidate.Pool.State != PoolActive {
		return "pool_not_active"
	}
	if !candidate.CapabilityMatched {
		return "capability_mismatch"
	}
	if candidate.Health.State == RouteUnreachable {
		return "route_unreachable"
	}
	if candidate.Health.State == RouteUnknown || candidate.Health.State == "" {
		return "route_unknown"
	}
	if candidate.Health.SampledAt.IsZero() || now.Sub(candidate.Health.SampledAt) > maxAge || (!candidate.Health.ExpiresAt.IsZero() && !now.Before(candidate.Health.ExpiresAt)) {
		return "route_stale"
	}
	if candidate.Capacity.Critical {
		return "resource_critical"
	}
	if candidate.Capacity.WorkerCount < 1 {
		return "no_worker"
	}
	if candidate.Capacity.SafeCapacity-candidate.Capacity.ActiveTasks < 1 {
		return "capacity_exhausted"
	}
	return ""
}

func routeQuality(health RouteHealth) float64 {
	success := clamp01(health.SuccessRate)
	connect := latencyQuality(health.ConnectTLSP95, 100*time.Millisecond)
	firstByte := latencyQuality(health.FirstByteP95, 250*time.Millisecond)
	throughput := clamp01(health.UploadBytesPerSecond / (10 * 1024 * 1024))
	jitter := latencyQuality(health.JitterP95, 100*time.Millisecond)
	return 0.35*success + 0.25*connect + 0.25*firstByte + 0.10*throughput + 0.05*jitter
}

func queueQuality(capacity CapacitySnapshot) float64 {
	available := capacity.SafeCapacity - capacity.ActiveTasks
	if available <= 0 {
		return 0
	}
	headroom := float64(available) / float64(max(capacity.SafeCapacity, 1))
	waitPenalty := 1 / (1 + capacity.EstimatedQueueWait.Seconds())
	return clamp01(0.6*headroom + 0.4*waitPenalty)
}

func latencyQuality(value, target time.Duration) float64 {
	if value <= 0 {
		return 0
	}
	return clamp01(1 / (1 + float64(value)/float64(target)))
}

func relativeImprovement(candidate, current float64) float64 {
	if current <= 0 {
		return 1
	}
	return (candidate - current) / current
}

func indexOfPool(candidates []scoredCandidate, poolID string) int {
	for index := range candidates {
		if candidates[index].candidate.Pool.ID == poolID {
			return index
		}
	}
	return -1
}

func clamp01(value float64) float64 {
	return math.Max(0, math.Min(1, value))
}
