// Package controller add by blacknc <blacknc@163.com> at 2023/11/14 16:24:17
package controller

import (
	"context"
	"dataservices/commons/http"
	"dataservices/commons/utils"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/atomic"
	"go.uber.org/zap"
)

var executorSystemload sync.Map

type ExecutorLoad struct {
	Instance   string
	Load       atomic.Float64
	UpdateTime time.Time
}

func Init() {
	utils.GOWithName("dataservice-gateway_systemload", func() {
		initD := 5 * time.Second
		maxD := 5 * time.Minute
		d := initD
		t := time.NewTimer(d)
		ctx := context.TODO()
		query := "dataservices_dataapi_executor_custom_system_load"
		logger := tilog.WithContextL(ctx)
		for {
			<-t.C

			metricOutput, err := http.MetricQuery(ctx, query, "")
			if err != nil {
				logger.Warn("query dataservice executor system load failed", zap.Error(err))
				d *= 2
				if d > maxD {
					d = maxD
				}
				t.Reset(d)
			} else {
				d = initD
			}
			for _, metricResult := range metricOutput {
				instance, _ := metricResult.Metric["instance"].(string)
				if instance == "" {
					logger.Warn("query dataservice executor system load got invalid instance", zap.Any("metric_result", metricResult))
					continue
				}
				if pos := strings.Index(instance, ":"); pos != -1 {
					instance = instance[:pos]
				}

				if len(metricResult.Value) != 2 {
					logger.Warn("query dataservice executor system load got invalid value", zap.Any("metric_result", metricResult))
					continue
				}
				value, _ := metricResult.Value[1].(string)
				systemLoad, err := strconv.ParseFloat(value, 64)
				if err != nil {
					logger.Warn("query dataservice executor system load got invalid value", zap.Any("metric_result", metricResult), zap.Error(err))
					continue
				}

				loadValue, _ := executorSystemload.Load(instance)
				executorLoad, _ := loadValue.(*ExecutorLoad)
				if executorLoad == nil {
					executorLoad = &ExecutorLoad{
						Instance:   instance,
						Load:       *atomic.NewFloat64(systemLoad),
						UpdateTime: time.Now(),
					}
				} else {
					executorLoad.Load.Store(systemLoad)
					executorLoad.UpdateTime = time.Now()
				}
				executorSystemload.Store(instance, executorLoad)
			}

			executorSystemload.Range(func(key, value any) bool {
				executorLoad, _ := value.(*ExecutorLoad)
				if executorLoad.UpdateTime.Before(time.Now().Add(-time.Second * 50)) {
					executorSystemload.Delete(key)
				}
				return true
			})

			t.Reset(d)
		}
	})
}

func IsExecutorLoadOK(ctx context.Context, instance string) bool {
	if pos := strings.Index(instance, ":"); pos != -1 {
		instance = instance[:pos]
	}
	loadValue, ok := executorSystemload.Load(instance)
	if !ok {
		return true
	}
	executorLoad, ok := loadValue.(*ExecutorLoad)
	if !ok {
		return true
	}
	if executorLoad.UpdateTime.Before(time.Now().Add(-time.Second * 10)) {
		return true
	}
	load := executorLoad.Load.Load()
	threshold := 0.68
	result := load <= threshold // (cpu:0.60 + mem:0.60 + load:1.0 + endpoint_load:0.5) / 4 = 0.68
	if !result {
		tilog.Info(ctx, "executor instance is overload", zap.String("instance", instance), zap.Float64("load", load), zap.Float64("load_threshold", threshold))
	}
	return result
}
