package kubepersistentvolumefillingup

import (
	"jirasop/model"
	"jirasop/pkg/infra/cliniccli"
	"jirasop/pkg/infra/config"
	"jirasop/pkg/infra/log"
	"strconv"
	"time"

	"github.com/go-logr/logr"
)

type SpaceUsageTrendHandler struct {
	logger    logr.Logger
	clinicCli *cliniccli.ClinicClient
	config    []config.AlertReasonParam
}

func NewSpaceUsageTrendHandler(logconf config.LogConfig,
	config []config.AlertReasonParam,
	clinicCli *cliniccli.ClinicClient) *SpaceUsageTrendHandler {
	logr := log.Logger.WithName("SpaceUsageTrend")
	return &SpaceUsageTrendHandler{logger: logr, clinicCli: clinicCli, config: config}
}

func (su *SpaceUsageTrendHandler) AnalyzeReason(issue *model.JiraWebhookEvent) (*model.AnalyzeReason, error) {
	judge := false
	issueStartAt := issue.Timestamp / 1000
	clusterID := issue.Issue.Fields.Unknowns["customfield_10616"].(string)
	tenantID := issue.Issue.Fields.Unknowns["customfield_10615"].(string)
	description := ``
	if clusterID != `` && tenantID != `` {
		metric1UsageTrend := su.config[0]["metric"].(string)
		step1UsageTrend := strconv.Itoa(su.config[0]["step"].(int))
		period1UsageTrend := su.config[0]["period"].(int)
		start := issueStartAt - int64(period1UsageTrend)*60
		end := issueStartAt

		// available size trend
		metrics, err := su.clinicCli.GetOneMetricRequest(metric1UsageTrend, clusterID, tenantID, start, end, step1UsageTrend+`m`)
		if err != nil {
			su.logger.Error(err, "fail to get metrics")
			return nil, err
		}
		growRatioArray := make([]float64, 0)
		for _, v := range metrics.Result {
			val := v.Values
			var preVal float64
			for i, v2 := range val {
				tsUnix := v2[0].(float64)
				tsValStr := v2[1].(string)
				tsVal, _ := strconv.ParseFloat(tsValStr, 64)
				tsValGB := tsVal / 1024 / 1024 / 1024
				description += `total *available* usage at ` + time.Unix(int64(tsUnix), 0).String() + ` is: ` + strconv.FormatFloat(tsValGB, 'f', 2, 64) + `GB`
				if i != 0 {
					trendRatio := (tsVal - preVal) / preVal * 100
					growRatioArray = append(growRatioArray, trendRatio)
					trendRatioStr := strconv.FormatFloat(trendRatio, 'f', 2, 64)
					description += `, compared with the value at previous timestamp, the *grow ratio* is *` + trendRatioStr + `* percentage
`
				} else {
					description += `
`
				}
				preVal = tsVal
			}
		}
		if IsAll[float64](growRatioArray, Negative) {
			judge = true
		}
		var total float64
		for _, v := range growRatioArray {
			total += v
		}
		meanRatio := total / float64(len(growRatioArray))
		estimated := float64(-100) / meanRatio
		description += `
Estimated: after *` + strconv.FormatFloat(estimated, 'f', 2, 64) + `* days the disk will be fully filled.
`

		// balance between TiKVs
		metric2UsageTrend := su.config[1]["metric"].(string)
		step2UsageTrend := strconv.Itoa(su.config[1]["step"].(int))
		period2UsageTrend := su.config[1]["period"].(int)
		start = issueStartAt - int64(period2UsageTrend)*60

		metrics, err = su.clinicCli.GetOneMetricRequest(metric2UsageTrend, clusterID, tenantID, start, end, step2UsageTrend+`m`)
		if err != nil {
			su.logger.Error(err, "fail to get metrics")
			return nil, err
		}
		var biggest float64
		var lowest float64
		for i, v := range metrics.Result {
			val := v.Values
			tsValStr := val[0][1].(string)
			tsVal, _ := strconv.ParseFloat(tsValStr, 64)
			if i == 0 {
				biggest = tsVal
				lowest = tsVal
				continue
			}
			if len(val) > 0 && tsVal > biggest {
				biggest = tsVal
			}
			if len(val) > 0 && tsVal < lowest {
				lowest = tsVal
			}
		}
		diff := (biggest - lowest) / lowest * 100
		if diff > 5 {
			judge = true
		}
		description += `
The most used TiKV is ` + strconv.FormatFloat(biggest/1024/1024/1024, 'f', 2, 64) + `GB, and the least used TiKV is ` + strconv.FormatFloat(lowest/1024/1024/1024, 'f', 2, 64) + `GB , and the diff is ` + strconv.FormatFloat(diff, 'f', 2, 64) + ` percentage.
`
		return &model.AnalyzeReason{Name: "SpaceUsageTrend", Is: judge, Description: description}, nil
	}
	return &model.AnalyzeReason{Name: "SpaceUsageTrend", Is: false, Description: "cluster ID or tenant ID is empty"}, nil
}

func IsAll[T interface{}](array []T, f func(T) bool) bool {
	for _, v := range array {
		if !f(v) {
			return false
		}
	}
	return true
}

func Negative(v float64) bool {
	return v < 0
}
