package schedulers

import (
	"context"
	"fmt"
	"hash/crc32"
	"time"

	commM "github.com/pingcap/test-infra/common/model"
	"github.com/pingcap/test-infra/services/tps/pkg/common"
)

type Backoff struct {
	now func() time.Time
}

func (s *Backoff) Name() string {
	return "Backoff"
}

const MinWaitTime = time.Hour
const RetryIncreaseTime = 30 * time.Minute
const MaxWaitTime = 3 * time.Hour
const MaxRandTime = 1 * time.Hour

func (s *Backoff) Filter(ctx context.Context, jobs []*common.ArgoJob) (result []*common.ArgoJob, err error) {
	for _, job := range jobs {
		if job.PlanExec.Status == commM.ExecutionStatusPrepareBackoff && s.now().Before(s.expectedWaitTime(job)) {
			job.TestbedSetup.Status.Schedule.ScheduleReason.Message = "backoff will wait before " + s.expectedWaitTime(job).String()
			continue
		}
		result = append(result, job)
	}
	return
}

func (s Backoff) expectedWaitTime(job *common.ArgoJob) time.Time {
	expectedWaitTime := MinWaitTime
	expectedWaitTime += time.Duration(job.PlanExec.RetryCnt-1) * RetryIncreaseTime
	if expectedWaitTime > MaxWaitTime {
		expectedWaitTime = MaxWaitTime
	}
	expectedWaitTime += genRandomSeconds(job)
	return job.PlanExec.StartedAt.Time().Add(expectedWaitTime)
}

func genRandomSeconds(job *common.ArgoJob) time.Duration {
	// pseudo random from job id*retry hash
	hashSource := []byte(fmt.Sprintf("%d%d", job.ID(), job.PlanExec.RetryCnt))
	randRate := float32(crc32.ChecksumIEEE(hashSource)%1000) / 1000.0
	return time.Duration(int(randRate * float32(MaxRandTime)))
}
