package strategy

import (
	"errors"
	"gitlink.org.cn/JointCloud/pcm-coordinator/internal/scheduler/common"
	"gitlink.org.cn/JointCloud/pcm-coordinator/internal/scheduler/service/collector"
	"gitlink.org.cn/JointCloud/pcm-coordinator/internal/storeLink"
	"strings"
)

const (
	RUNNING_TASK_WEIGHT     = 300
	TASK_PREDICTED_WEIGHT   = 0.03
	UNLIMITED_PREDICTED_NUM = 10000
)

type LeastLoadFirst struct {
	replicas int32
	clusters []*CLusterLoad
}

type CLusterLoad struct {
	ClusterId        string
	TaskRunningNum   int64
	TaskPredictedNum int64
}

func NewLeastLoadFirst(replicas int32, resources []*collector.ResourceSpec) *LeastLoadFirst {
	var clusters []*CLusterLoad

	for _, resource := range resources {
		if resource.ClusterId == "" {
			continue
		}
		cluster := &CLusterLoad{
			ClusterId: resource.ClusterId,
		}
		var balance float64
		var rate float64
		for _, res := range resource.Resources {
			r, ok := res.(*collector.ClusterResource)
			if !ok {
				continue
			}
			switch r.Resource.Type {
			case storeLink.RUNNINGTASK:
				num, ok := r.Resource.Total.Value.(int64)
				if !ok {
					continue
				}
				cluster.TaskRunningNum = num
			case strings.ToUpper(storeLink.BALANCE):
				b, ok := r.Resource.Total.Value.(float64)
				if !ok {
					continue
				}
				balance = b
			case strings.ToUpper(storeLink.RATE):
				rt, ok := r.Resource.Total.Value.(float64)
				if !ok {
					continue
				}
				rate = rt
			default:
				continue
			}
		}

		if balance == 0 || rate == 0 {
			continue
		}

		var predictedNum int64
		if balance == -1 {
			// unlimited
			predictedNum = UNLIMITED_PREDICTED_NUM
		} else {
			predictedNum = int64(common.RoundFloat(balance/rate, 0))
		}

		cluster.TaskPredictedNum = predictedNum
		clusters = append(clusters, cluster)
	}

	return &LeastLoadFirst{replicas: replicas, clusters: clusters}
}

func (l *LeastLoadFirst) Schedule() ([]*AssignedCluster, error) {
	if l.replicas < 1 {
		return nil, errors.New("replicas must be greater than 0")
	}

	swMap := make(map[string]int32, 0)
	for _, cluster := range l.clusters {
		var runningTaskWeight int64
		if cluster.TaskRunningNum != 0 {
			runningTaskWeight = RUNNING_TASK_WEIGHT / cluster.TaskRunningNum
		} else {
			runningTaskWeight = RUNNING_TASK_WEIGHT * 2
		}
		weight := runningTaskWeight + int64(float64(cluster.TaskPredictedNum)*TASK_PREDICTED_WEIGHT)
		swMap[cluster.ClusterId] = int32(weight)
	}

	sw := NewStaticWeightStrategy(swMap, l.replicas)
	clusters, err := sw.Schedule()
	if err != nil {
		return nil, err
	}

	return clusters, nil
}
