package core

import (
	"fmt"
	"github.com/guo-shaoge/tiflash-autoscale/pkg/meta"
	"go.uber.org/zap"
	"time"
)

const warmupPoolName = "WarmupPool"

type WarmupPool struct {
	basePool

	poolSize uint32
}

func NewWarmupPool() *WarmupPool {
	res := WarmupPool{
		basePool: *newBasePool(warmupPoolName),
	}
	return &res
}

func (w *WarmupPool) scanPool(logger *zap.Logger, pendingTimeout uint) []string {
	podsToDelete := make([]string, 0, 5)
	var notifyAvailableUnassignedPods bool

	func() {
		w.basePool.mu.Lock()
		defer w.basePool.mu.Unlock()

		for podName, podDesc := range w.basePool.podsMap {
			status := podDesc.GetStatus()
			switch status {
			case meta.PodStatusRunning, meta.PodStatusSharing, meta.PodStatusReadyUnassign, meta.PodStatusUnassigning:
				logger.Error("unexpected pod in WarmupPool", zap.String("pod", podName), zap.String("status", podDesc.GetStatusStr()))
				podsToDelete = append(podsToDelete, podName)
				delete(w.basePool.podsMap, podName)
			case meta.PodStatusLogicalError, meta.PodStatusNeedDelete:
				logger.Info("got pod NeedDelete in WarmupPool", zap.String("pod", podName), zap.String("status", podDesc.GetStatusStr()))
				podsToDelete = append(podsToDelete, podName)
				delete(w.basePool.podsMap, podName)
			case meta.PodStatusUnassigned:
				notifyAvailableUnassignedPods = true
			case meta.PodStatusPending:
				lastPendingTs := podDesc.GetLastPendingTs()
				deadline := lastPendingTs.Add(time.Duration(pendingTimeout) * time.Second)
				if time.Now().After(deadline) {
					podsToDelete = append(podsToDelete, podName)
					delete(w.basePool.podsMap, podName)
					logger.Info("got pending too long pod", zap.String("pod", podName), zap.Any("lastPendingTs", podDesc.GetLastPendingTs()),
						zap.Any("deadline", deadline))
				}
			case meta.PodStatusDeleted:
				logger.Info("got pod Deleted in WarmupPool", zap.String("pod", podName))
				delete(w.basePool.podsMap, podName)
			default:
				panic(fmt.Sprintf("unexpected pod status: %v", status))
			}
		}
	}()
	if notifyAvailableUnassignedPods {
		// TODO maybe too slow notify every 5s?
		w.NotifyNewWarmupPod()
	}
	return podsToDelete
}

func (w *WarmupPool) SyncRemovePodsByNum(num uint, filter meta.PodsFilter,
	p *PodMgr, logger *zap.Logger) []*meta.PodDesc {
	res := w.RemovePodsByNum(num, filter)

	// TODO maybe below also update failCnt
	// TODO trigger warmupLoop()
	failCnt := int(num) - len(res)
	if failCnt > 0 {
		p.failCnt.Add(int32(failCnt))
	} else if failCnt < 0 {
		logger.Error("allocate more pods than expected", zap.Any("num", num), zap.Any("pods", len(res)))
	}

	if len(res) > 0 {
		return res
	}

	w.basePool.mu.Lock()
	defer w.basePool.mu.Unlock()
	for {
		res = w.RemovePodsByNumWithoutLock(num, filter)
		if len(res) >= 1 {
			break
		}
		// todo better add a timeout to avoid wait forever.
		w.basePool.newWarmupPodCV.Wait()
	}
	return res
}

func (w *WarmupPool) NotifyNewWarmupPod() {
	w.basePool.mu.Lock()
	defer w.basePool.mu.Unlock()
	w.basePool.newWarmupPodCV.Signal()
}
