//nolint:unused // ignore this file
package tidbmanager

import "sync"

type freeCache struct {
	sync.RWMutex
	inited bool
	// format: nodename: podname: pod info
	nodePodMap map[string]map[string]freePodInfo
}

type freePodState uint8

const (
	freePodAvailable freePodState = iota
	freePodActivating
	freePodQuarantined
)

type freePodInfo struct {
	podIP string
	state freePodState
}

func (s freePodState) String() string {
	switch s {
	case freePodAvailable:
		return "available"
	case freePodActivating:
		return "activating"
	case freePodQuarantined:
		return "quarantined"
	default:
		return "unknown"
	}
}

func (info freePodInfo) isAvailable() bool {
	return info.state == freePodAvailable
}

func (info freePodInfo) isVisible(includeActivating bool) bool {
	return info.state == freePodAvailable || includeActivating && info.state == freePodActivating
}

func (info freePodInfo) countsAsCapacity() bool {
	return info.state != freePodQuarantined
}

func (fc *freeCache) init(podMap map[string]podInfo) {
	if fc.inited {
		panic("freeCache already inited before")
	}

	fc.nodePodMap = map[string]map[string]freePodInfo{}
	for name, info := range podMap {
		if fc.nodePodMap[info.nodeName] == nil {
			fc.nodePodMap[info.nodeName] = map[string]freePodInfo{}
		}
		fc.nodePodMap[info.nodeName][name] = freePodInfo{podIP: info.podIP}
	}
	fc.inited = true
}

func (fc *freeCache) setPod(nodeName, podName, podIP string) {
	fc.Lock()
	defer fc.Unlock()

	state := freePodAvailable
	for oldNodeName, podMap := range fc.nodePodMap {
		if info, ok := podMap[podName]; ok {
			if info.podIP == podIP {
				state = info.state
			}
			delete(podMap, podName)
			if len(podMap) == 0 {
				delete(fc.nodePodMap, oldNodeName)
			}
		}
	}
	fc.setPodStateLocked(nodeName, podName, podIP, state)
}

func (fc *freeCache) setPodState(nodeName, podName, podIP string, state freePodState) bool {
	fc.Lock()
	defer fc.Unlock()

	for oldNodeName, podMap := range fc.nodePodMap {
		info, ok := podMap[podName]
		if !ok {
			continue
		}
		if info.podIP != podIP {
			return false
		}
		delete(podMap, podName)
		if len(podMap) == 0 {
			delete(fc.nodePodMap, oldNodeName)
		}
	}
	fc.setPodStateLocked(nodeName, podName, podIP, state)
	return true
}

// setPodStateLocked requires the freeCache write lock to be held.
func (fc *freeCache) setPodStateLocked(nodeName, podName, podIP string, state freePodState) {
	if fc.nodePodMap[nodeName] == nil {
		fc.nodePodMap[nodeName] = map[string]freePodInfo{}
	}
	fc.nodePodMap[nodeName][podName] = freePodInfo{podIP: podIP, state: state}
}

func (fc *freeCache) tryResetActivating(nodeName, podName, podIP string) {
	fc.Lock()
	defer fc.Unlock()

	if podMap := fc.nodePodMap[nodeName]; podMap != nil {
		if info, ok := podMap[podName]; ok && info.podIP == podIP && info.state == freePodActivating {
			info.state = freePodAvailable
			podMap[podName] = info
		}
	}
}

func (fc *freeCache) quarantinePodIfIP(podName, expectedIP string) {
	fc.Lock()
	defer fc.Unlock()

	for _, podMap := range fc.nodePodMap {
		if info, ok := podMap[podName]; ok && info.podIP == expectedIP {
			info.state = freePodQuarantined
			podMap[podName] = info
			return
		}
	}
}

func (fc *freeCache) removePod(podName string) {
	fc.Lock()
	defer fc.Unlock()

	for nodeName, podMap := range fc.nodePodMap {
		if _, ok := podMap[podName]; !ok {
			continue
		}
		delete(podMap, podName)
		if len(podMap) == 0 {
			delete(fc.nodePodMap, nodeName)
		}
		return
	}
}

func (fc *freeCache) removePodIfIP(podName, expectedIP string) {
	fc.Lock()
	defer fc.Unlock()

	for nodeName, podMap := range fc.nodePodMap {
		if info, ok := podMap[podName]; ok && info.podIP == expectedIP {
			delete(podMap, podName)
			if len(podMap) == 0 {
				delete(fc.nodePodMap, nodeName)
			}
			return
		}
	}
}

func (fc *freeCache) removePodByNodeName(nodeName, podName string) {
	fc.Lock()
	defer fc.Unlock()

	if podMap := fc.nodePodMap[nodeName]; podMap != nil {
		delete(podMap, podName)
		if len(podMap) == 0 {
			delete(fc.nodePodMap, nodeName)
		}
	}
}

// assignOneOnNode is no lock for internal use
func (fc *freeCache) assignOneOnNode(nodeName string) (podName, podIP string) {
	if podMap := fc.nodePodMap[nodeName]; podMap != nil {
		for name, info := range podMap {
			if !info.isAvailable() {
				continue
			}
			info.state = freePodActivating
			podMap[name] = info
			return name, info.podIP
		}
	}
	return
}

func (fc *freeCache) assignOne(worstNodes []string) (podName, podIP string) {
	fc.Lock()
	defer fc.Unlock()

NEXT:
	for nodeName, podMap := range fc.nodePodMap {
		// skip worstNodes
		for _, worstNode := range worstNodes {
			if nodeName == worstNode {
				continue NEXT
			}
		}

		for name, info := range podMap {
			if !info.isAvailable() {
				continue
			}
			info.state = freePodActivating
			podMap[name] = info
			return name, info.podIP
		}
	}

	// only choice worstNodes
	for _, worstNode := range worstNodes {
		if podMap := fc.nodePodMap[worstNode]; podMap != nil {
			for name, info := range podMap {
				if !info.isAvailable() {
					continue
				}
				info.state = freePodActivating
				podMap[name] = info
				return name, info.podIP
			}
		}
	}
	return
}

func (fc *freeCache) getPod(podName string) string {
	fc.RLock()
	defer fc.RUnlock()

	for _, podMap := range fc.nodePodMap {
		if info, ok := podMap[podName]; ok {
			return info.podIP
		}
	}
	return ""
}

func (fc *freeCache) getPodByNodeName(nodeName, podName string) string {
	fc.RLock()
	defer fc.RUnlock()

	return fc.nodePodMap[nodeName][podName].podIP
}

func (fc *freeCache) getSize() int {
	fc.RLock()
	defer fc.RUnlock()

	size := 0
	for _, podMap := range fc.nodePodMap {
		for _, info := range podMap {
			if info.countsAsCapacity() {
				size++
			}
		}
	}
	return size
}

func (fc *freeCache) walk(f func(nodeName, podName string, podInfo freePodInfo) bool) {
	fc.RLock()
	defer fc.RUnlock()

	for nodeName, podMap := range fc.nodePodMap {
		for podName, info := range podMap {
			if !f(nodeName, podName, info) {
				return
			}
		}
	}
}

func (fc *freeCache) walkNode(f func(nodeName string, podMap map[string]freePodInfo) bool) {
	fc.RLock()
	defer fc.RUnlock()

	for nodeName, podMap := range fc.nodePodMap {
		if !f(nodeName, podMap) {
			return
		}
	}
}

func (fc *freeCache) walkNodeByLock(f func(nodeName string, podMap map[string]freePodInfo) bool, deferF func()) {
	fc.Lock()
	defer fc.Unlock()

	if deferF != nil {
		defer deferF()
	}
	for nodeName, podMap := range fc.nodePodMap {
		if !f(nodeName, podMap) {
			return
		}
	}
}

func (fc *freeCache) dumpStatus(data map[string][]interface{}) {
	fc.RLock()
	defer fc.RUnlock()

	type podStatus struct {
		PodIP        string `json:"podIP"`
		IsActivating bool   `json:"isActivating"`
		State        string `json:"state"`
	}

	for nodeName, podMap := range fc.nodePodMap {
		pods := make(map[string]podStatus, len(podMap))
		for podName, info := range podMap {
			pods[podName] = podStatus{
				PodIP:        info.podIP,
				IsActivating: info.state == freePodActivating,
				State:        info.state.String(),
			}
		}
		item := struct {
			Node string               `json:"node"`
			Pods map[string]podStatus `json:"pods"`
		}{
			Node: nodeName,
			Pods: pods,
		}
		data["freepod"] = append(data["freepod"], item)
	}
}
