package core

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

const unassigningPoolName = "UnassigningPool"

type UnassigningPool struct {
	basePool
}

func NewUnassigningPool() *UnassigningPool {
	res := UnassigningPool{
		basePool: *newBasePool(unassigningPoolName),
	}
	return &res
}

// AddAndTriggerUnassign add it to unassign pool and send unassign rpc.
// called by PodMgr.podScannerLoop(to move pods of ReadyUnassign from SharePool/VIPPool to UnassigningPool)
func (s *UnassigningPool) AddAndTriggerUnassign(pods meta.PodsMap, logger *zap.Logger) {
	podsSlice := make([]*meta.PodDesc, 0, len(pods))
	for _, podDesc := range pods {
		podsSlice = append(podsSlice, podDesc)

		if !podDesc.CheckAndToUnassigning() {
			logger.Error("change pod to unassigning failed, will change to LogicalError",
				zap.Any("pod", podDesc.ToString()))
			podDesc.ToLogicalError()
			continue
		}

		s.triggerUnassign(podDesc, logger)
	}

	_, addMsg := s.basePool.AddPods(podsSlice)
	if len(addMsg) != 0 {
		logger.Error("add pods to unassigning failed, maybe got duplicated pods. cannot handle, ignore!!",
			zap.String("addMsg", addMsg))
	}

	return
}

func (s *UnassigningPool) triggerUnassign(pod *meta.PodDesc, logger *zap.Logger) {
	go func(podDesc *meta.PodDesc) {
		resp, err := podDesc.SendUnassignRPC(logger)
		if err != nil || (resp != nil && resp.HasErr) {
			logger.Error("unassign pod failed and change it to Error", zap.Any("pod", podDesc.GetName()))
			podDesc.ToNeedDelete()
		} else {
			podDesc.ToUnassigned()
		}
	}(pod)
}

// move pods from UnassigningPool to WarmupPool.
// called by PodMgr.podScannerLoop(to move pods of Unassigned from UnassignPool to WarmupPool)
func (s *UnassigningPool) scanPool(logger *zap.Logger) []*meta.PodDesc {
	podsToWarmupPool := make([]*meta.PodDesc, 0, 5)

	s.basePool.mu.Lock()
	defer s.basePool.mu.Unlock()
	for podName, podDesc := range s.basePool.podsMap {
		status := podDesc.GetStatus()
		switch status {
		case meta.PodStatusRunning, meta.PodStatusSharing, meta.PodStatusReadyUnassign:
			// because when add into UnassigningPool, will be changed to PodStatusUnassigning first.
			logger.Error("unexpected pods in UnassigningPool, set to LogicalError and trigger Unassign",
				zap.String("pod", podName), zap.String("status", podDesc.GetStatusStr()))
			podDesc.ToLogicalError()
			s.triggerUnassign(podDesc, logger)
		case meta.PodStatusLogicalError:
			// nop
			// will try to unassign first, if unassign fails too many times, will become NeedDelete and
			// put to WarmupPool, and will be deleted.
		case meta.PodStatusNeedDelete:
			logger.Info("got NeedDelete pod in UnassigningPool, move to WarmupPool",
				zap.String("pod", podName), zap.String("status", podDesc.GetStatusStr()))
			podsToWarmupPool = append(podsToWarmupPool, podDesc)
			delete(s.basePool.podsMap, podName)
		case meta.PodStatusUnassigning:
			// nop
		case meta.PodStatusUnassigned, meta.PodStatusPending:
			logger.Info("got unassigned or pending pod, move to WarmupPool",
				zap.String("pod", podName), zap.Any("status", podDesc.GetStatusStr()))
			podsToWarmupPool = append(podsToWarmupPool, podDesc)
			delete(s.basePool.podsMap, podName)
		case meta.PodStatusDeleted:
			delete(s.basePool.podsMap, podName)
		default:
			panic(fmt.Sprintf("unexpected pod status: %v", status))
		}
	}
	return podsToWarmupPool
}
