package executor

import (
	"container/heap"
	"sync"
	"time"

	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/base"
)

type ActionTasks []*ActionTask

func (tasks ActionTasks) Clone() ActionTasks {
	var clone ActionTasks
	for _, it := range tasks {
		clone = append(clone, it.Clone())
	}
	return clone
}

func (tasks ActionTasks) CloneableClone() base.Cloneable {
	return tasks.Clone()
}

type ActionTaskScheduler interface {
	Add(tasks ...*ActionTask)
	Len() int
	PopToBeRuns(runningActionTasks map[string]*ActionTask) ActionTasks
}

type SafeWrapScheduler struct {
	origin ActionTaskScheduler
	locker sync.Mutex
}

func NewSafeWrapScheduler(origin ActionTaskScheduler) *SafeWrapScheduler {
	return &SafeWrapScheduler{
		origin: origin,
	}
}

func (scheduler *SafeWrapScheduler) Add(actionTasks ...*ActionTask) {
	scheduler.locker.Lock()
	defer scheduler.locker.Unlock()
	scheduler.origin.Add(actionTasks...)
}

func (scheduler *SafeWrapScheduler) ChangeScheduler(origin ActionTaskScheduler) {
	scheduler.locker.Lock()
	defer scheduler.locker.Unlock()
	scheduler.origin = origin
}

func (scheduler *SafeWrapScheduler) Len() int {
	scheduler.locker.Lock()
	defer scheduler.locker.Unlock()
	return scheduler.origin.Len()
}

func (scheduler *SafeWrapScheduler) PopToBeRuns(runningActionTasks map[string]*ActionTask) ActionTasks {
	scheduler.locker.Lock()
	defer scheduler.locker.Unlock()
	return scheduler.origin.PopToBeRuns(runningActionTasks)
}

type FifoSigleThreadScheduler struct {
	queue []*ActionTask
}

func NewFifoSigleThreadScheduler() *FifoSigleThreadScheduler {
	return &FifoSigleThreadScheduler{}
}

func (scheduler *FifoSigleThreadScheduler) Add(actionTasks ...*ActionTask) {
	scheduler.queue = append(scheduler.queue, actionTasks...)
}

func (scheduler *FifoSigleThreadScheduler) Len() int {
	return len(scheduler.queue)
}

func (scheduler *FifoSigleThreadScheduler) PopToBeRuns(runningActionTasks map[string]*ActionTask) ActionTasks {
	if len(scheduler.queue) == 0 {
		return nil
	}
	actionTasks := []*ActionTask{scheduler.queue[0]}
	scheduler.queue = scheduler.queue[1:]
	return actionTasks
}

type FullSpeedScheduler struct {
	queue []*ActionTask
}

func NewFullSpeedScheduler() *FullSpeedScheduler {
	return &FullSpeedScheduler{}
}

func (scheduler *FullSpeedScheduler) Add(actionTasks ...*ActionTask) {
	scheduler.queue = append(scheduler.queue, actionTasks...)
}

func (scheduler *FullSpeedScheduler) Len() int {
	return len(scheduler.queue)
}

func (scheduler *FullSpeedScheduler) PopToBeRuns(runningActionTasks map[string]*ActionTask) ActionTasks {
	if len(scheduler.queue) == 0 {
		return nil
	}
	actionTasks := scheduler.queue
	scheduler.queue = nil
	return actionTasks
}

type ActionTaskPriorQueueCmpFn func(a *ActionTask, b *ActionTask) bool

type ActionTaskPriorQueueScheduler struct {
	queue       ActionTaskPriorQueue
	concurrency uint
}

func NewActionTaskPriorQueueScheduler(cmpLessFn ActionTaskPriorQueueCmpFn, concurrency uint) *ActionTaskPriorQueueScheduler {
	return &ActionTaskPriorQueueScheduler{
		queue: ActionTaskPriorQueue{
			cmpLessFn: cmpLessFn,
		},
		concurrency: concurrency,
	}
}

func (scheduler *ActionTaskPriorQueueScheduler) Add(actionTasks ...*ActionTask) {
	for _, actionTask := range actionTasks {
		heap.Push(&scheduler.queue, actionTask)
	}
}

func (scheduler *ActionTaskPriorQueueScheduler) Len() int {
	return scheduler.queue.Len()
}

func (scheduler *ActionTaskPriorQueueScheduler) PopToBeRuns(runningActionTasks map[string]*ActionTask) ActionTasks {
	var actionTasks []*ActionTask
	for i := uint(0); i < scheduler.concurrency-uint(len(runningActionTasks)); i++ {
		actionTasks = append(actionTasks, heap.Pop(&scheduler.queue).(*ActionTask))
	}
	return actionTasks
}

type ActionTaskPriorQueue struct {
	cmpLessFn ActionTaskPriorQueueCmpFn
	queue     []*ActionTask
}

func (queue *ActionTaskPriorQueue) Len() int {
	return len(queue.queue)
}

func (queue *ActionTaskPriorQueue) Less(i, j int) bool {
	return queue.cmpLessFn(queue.queue[i], queue.queue[j])
}

func (queue *ActionTaskPriorQueue) Swap(i, j int) {
	queue.queue[i], queue.queue[j] = queue.queue[j], queue.queue[i]
}

func (queue *ActionTaskPriorQueue) Push(x any) {
	queue.queue = append(queue.queue, x.(*ActionTask))
}

func (queue *ActionTaskPriorQueue) Pop() any {
	n := len(queue.queue)
	x := queue.queue[n-1]
	queue.queue = queue.queue[:n-1]
	return x
}

type BackOffActionTask struct {
	readyTs time.Time
	origin  *ActionTask
}

type BackOffScheduler struct {
	backOffDelay time.Duration
	queue        []*ActionTask
	backOffQueue []*BackOffActionTask
	history      map[string]struct{}
}

func NewBackOffScheduler(delay time.Duration) *BackOffScheduler {
	return &BackOffScheduler{
		backOffDelay: delay,
		queue:        make([]*ActionTask, 0),
		history:      make(map[string]struct{}),
	}
}

func (scheduler *BackOffScheduler) Add(actionTasks ...*ActionTask) {
	curr := time.Now().Local()
	for _, action := range actionTasks {
		id := action.Id()
		if _, ok := scheduler.history[id]; !ok {
			scheduler.queue = append(scheduler.queue, action)
		} else {
			backOffAction := &BackOffActionTask{
				origin:  action,
				readyTs: curr.Add(scheduler.backOffDelay),
			}
			scheduler.backOffQueue = append(scheduler.backOffQueue, backOffAction)
		}
		scheduler.history[id] = struct{}{}
	}
	// protect the mem usage
	if len(scheduler.history) >= 10000 {
		clear(scheduler.history)
	}
}

func (scheduler *BackOffScheduler) Len() int {
	return len(scheduler.queue) + len(scheduler.backOffQueue)
}

func (scheduler *BackOffScheduler) PopToBeRuns(runningActionTasks map[string]*ActionTask) ActionTasks {
	if len(scheduler.queue)+len(scheduler.backOffQueue) == 0 {
		return nil
	}
	out := scheduler.queue
	scheduler.queue = nil
	if len(scheduler.backOffQueue) > 0 {
		remainIndex := 0
		curr := time.Now().Local()
		for _, action := range scheduler.backOffQueue {
			if action.readyTs.After(curr) {
				break
			}
			out = append(out, action.origin)
			remainIndex++
		}
		scheduler.backOffQueue = scheduler.backOffQueue[remainIndex:]
	}
	return out
}
