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

import (
	"math/rand"
	"strings"
	"sync"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/aws-shared-provider/pkg/types"
)

type tidbInfo struct {
	endpoints            []string // format "podname,podip"
	replicas             int
	comment              string
	endpointsModRevision int64
	replicasModRevision  int64
}

func newTidbInfo(endpoints string, replicas int, endpointsModRevision int64) tidbInfo {
	var eps []string
	var comment string
	if endpoints != "" {
		parts := strings.SplitN(endpoints, "//", 2) //nolint:mnd // not need
		if len(parts) > 1 {
			comment = parts[1]
		}
		if parts[0] != "" {
			eps = strings.Split(parts[0], ";")
		}
	}
	return tidbInfo{
		endpoints:            eps,
		replicas:             replicas,
		comment:              comment,
		endpointsModRevision: endpointsModRevision,
	}
}

func (t *tidbInfo) modRevision() int64 {
	return max(t.endpointsModRevision, t.replicasModRevision)
}

// getEndpoints return "podname1,podip1;podname2,podip2//comment"
func (t *tidbInfo) getEndpoints() string {
	endpoints := strings.Join(t.endpoints, ";")
	if t.comment != "" {
		endpoints += "//" + t.comment
	}
	return endpoints
}

func (t *tidbInfo) setEndpoints(endpoints string, modRevision int64) {
	parts := strings.SplitN(endpoints, "//", 2) //nolint:mnd // not need
	if len(parts) > 1 {
		t.comment = parts[1]
	} else {
		t.comment = ""
	}
	if parts[0] != "" {
		t.endpoints = strings.Split(parts[0], ";")
	} else {
		t.endpoints = nil
	}
	t.endpointsModRevision = modRevision
}

func (t *tidbInfo) setComment(comment string) {
	t.comment = comment
}

func (t *tidbInfo) getRandEndpoint() (endpoint string, err error) {
	count := len(t.endpoints)
	if count == 0 {
		return "", errors.New("no endpoint")
	}

	if count > 1 {
		endpoint = t.endpoints[rand.Intn(len(t.endpoints))]
	} else {
		endpoint = t.endpoints[0]
	}
	return
}

func (t *tidbInfo) findEndpoint(endpoint string) bool {
	for _, ep := range t.endpoints {
		if ep == endpoint {
			return true
		}
	}
	return false
}

func (t *tidbInfo) appendEndpoint(endpoint string) {
	if endpoint != "" {
		t.endpoints = append(t.endpoints, endpoint)
		t.comment = ""
	}
}

func (t *tidbInfo) removeEndpoint(endpoint string) bool {
	endpoints := make([]string, 0, len(t.endpoints))
	removed := false
	for _, ep := range t.endpoints {
		if ep == endpoint {
			removed = true
			continue
		}
		endpoints = append(endpoints, ep)
	}
	t.endpoints = endpoints
	if removed {
		t.comment = ""
	}
	return removed
}

func (t *tidbInfo) diffEndpoint(pre tidbInfo) (inc, dec []string) {
	preMap := make(map[string]struct{}, len(pre.endpoints))
	for _, endpoint := range pre.endpoints {
		preMap[endpoint] = struct{}{}
	}

	for _, endpoint := range t.endpoints {
		if _, ok := preMap[endpoint]; !ok {
			inc = append(inc, endpoint)
		} else {
			delete(preMap, endpoint)
		}
	}
	for endpoint := range preMap {
		dec = append(dec, endpoint)
	}

	return
}

// endpoints in assignCache must be unique. But we not validate here
type assignCache struct {
	sync.RWMutex
	inited bool

	tokenMap map[string]tidbInfo
	// format revision: "podname,podip": "token"
	revisionEndpointMap map[string]map[string]string
}

func (ac *assignCache) setRevisionEndpoint(endpoint, token string) {
	// no lock for internal use
	rlt := strings.Split(strings.TrimPrefix(endpoint, types.TidbServerNamePrefix), "-")
	if len(rlt) > 0 {
		revision := rlt[0]
		if ac.revisionEndpointMap[revision] == nil {
			ac.revisionEndpointMap[revision] = map[string]string{endpoint: token}
		} else {
			ac.revisionEndpointMap[revision][endpoint] = token
		}
	} else {
		if ac.revisionEndpointMap["__NO_REVISION__"] == nil {
			ac.revisionEndpointMap["__NO_REVISION__"] = map[string]string{endpoint: token}
		} else {
			ac.revisionEndpointMap["__NO_REVISION__"][endpoint] = token
		}
	}
}

func (ac *assignCache) removeRevisionEndpoint(endpoint string) {
	// no lock for internal use
	rlt := strings.Split(strings.TrimPrefix(endpoint, types.TidbServerNamePrefix), "-")
	if len(rlt) > 0 {
		revision := rlt[0]
		delete(ac.revisionEndpointMap[revision], endpoint)
		if len(ac.revisionEndpointMap[revision]) == 0 {
			delete(ac.revisionEndpointMap, revision)
		}
		return
	}

	delete(ac.revisionEndpointMap["__NO_REVISION__"], endpoint)
	if len(ac.revisionEndpointMap["__NO_REVISION__"]) == 0 {
		delete(ac.revisionEndpointMap, "__NO_REVISION__")
	}
}

func (ac *assignCache) getRevisionEndpoint(endpoint string) string {
	// no lock for internal use
	rlt := strings.Split(strings.TrimPrefix(endpoint, types.TidbServerNamePrefix), "-")
	if len(rlt) > 0 {
		revision := rlt[0]
		return ac.revisionEndpointMap[revision][endpoint]
	}

	return ac.revisionEndpointMap["__NO_REVISION__"][endpoint]
}

func (ac *assignCache) init(tokenMap map[string]tidbInfo) {
	if ac.inited {
		panic("assignCache already inited before")
	}

	ac.tokenMap = tokenMap
	ac.revisionEndpointMap = make(map[string]map[string]string)
	for token, tInfo := range tokenMap {
		for _, endpoint := range tInfo.endpoints {
			if endpoint != "" {
				ac.setRevisionEndpoint(endpoint, token)
			}
		}
	}
	ac.inited = true
}

func (ac *assignCache) getTidbInfo(token string) (tidbInfo tidbInfo, ok bool) {
	ac.RLock()
	defer ac.RUnlock()

	tidbInfo, ok = ac.tokenMap[token]
	return
}

func (ac *assignCache) modifyTidbInfoReplicas(token string, replicas int, modRevision int64) (preTidbInfo tidbInfo, exist bool) {
	ac.Lock()
	defer ac.Unlock()

	info, exist := ac.tokenMap[token]
	if !exist {
		return
	}
	preTidbInfo = info
	info.replicas = replicas
	info.replicasModRevision = modRevision
	ac.tokenMap[token] = info
	return
}

func (ac *assignCache) modifyTidbInfoEndpoints(
	token string, endpoints string, modRevision int64,
) (preTidbInfo tidbInfo, exist bool) {
	ac.Lock()
	defer ac.Unlock()

	info, exist := ac.tokenMap[token]
	if !exist {
		return
	}
	preTidbInfo = info
	for _, endpoint := range preTidbInfo.endpoints {
		if endpoint != "" {
			ac.removeRevisionEndpoint(endpoint)
		}
	}
	info.setEndpoints(endpoints, modRevision)
	for _, endpoint := range info.endpoints {
		if endpoint != "" {
			ac.setRevisionEndpoint(endpoint, token)
		}
	}
	ac.tokenMap[token] = info
	return
}

func (ac *assignCache) modifyTidbInfo(token string, tidbInfo tidbInfo) (preTidbInfo tidbInfo, exist bool) {
	ac.Lock()
	defer ac.Unlock()

	preTidbInfo, exist = ac.tokenMap[token]
	if !exist {
		return
	}
	for _, endpoint := range preTidbInfo.endpoints {
		if endpoint != "" {
			ac.removeRevisionEndpoint(endpoint)
		}
	}
	for _, endpoint := range tidbInfo.endpoints {
		if endpoint != "" {
			ac.setRevisionEndpoint(endpoint, token)
		}
	}
	ac.tokenMap[token] = tidbInfo
	return
}

func (ac *assignCache) setTidbInfoReplicas(token string, replicas int) (preTidbInfo tidbInfo) {
	ac.Lock()
	defer ac.Unlock()

	info := ac.tokenMap[token]
	preTidbInfo = info
	info.replicas = replicas
	ac.tokenMap[token] = info
	return
}

func (ac *assignCache) setTidbInfoEndpoints(token string, endpoints string, modRevision int64) (preTidbInfo tidbInfo) {
	ac.Lock()
	defer ac.Unlock()

	info, ok := ac.tokenMap[token]
	if ok {
		preTidbInfo = info
		for _, endpoint := range preTidbInfo.endpoints {
			if endpoint != "" {
				ac.removeRevisionEndpoint(endpoint)
			}
		}
	}
	info.setEndpoints(endpoints, modRevision)
	for _, endpoint := range info.endpoints {
		if endpoint != "" {
			ac.setRevisionEndpoint(endpoint, token)
		}
	}
	ac.tokenMap[token] = info
	return
}

func (ac *assignCache) setTidbInfo(token string, tidbInfo tidbInfo) (preTidbInfo tidbInfo) {
	ac.Lock()
	defer ac.Unlock()

	if info, ok := ac.tokenMap[token]; ok {
		preTidbInfo = info
		for _, endpoint := range preTidbInfo.endpoints {
			if endpoint != "" {
				ac.removeEndpoint(endpoint)
			}
		}
	}
	for _, endpoint := range tidbInfo.endpoints {
		if endpoint != "" {
			ac.setRevisionEndpoint(endpoint, token)
		}
	}
	ac.tokenMap[token] = tidbInfo
	return
}

func (ac *assignCache) removeToken(token string) (preTidbInfo tidbInfo, removed bool) {
	ac.Lock()
	defer ac.Unlock()

	preTidbInfo, ok := ac.tokenMap[token]
	if !ok {
		return
	}
	for _, endpoint := range preTidbInfo.endpoints {
		if endpoint != "" {
			ac.removeRevisionEndpoint(endpoint)
		}
	}
	delete(ac.tokenMap, token)
	removed = true
	return
}

func (ac *assignCache) removeEndpointByToken(token, endpoint string) bool {
	if endpoint == "" {
		return false
	}

	ac.Lock()
	defer ac.Unlock()

	info, ok := ac.tokenMap[token]
	if !ok {
		return false
	}
	if !info.removeEndpoint(endpoint) {
		return false
	}
	ac.tokenMap[token] = info
	// if specified token not equal from getRevisionEndpoint, just remove in tokenMap only
	if token == ac.getRevisionEndpoint(endpoint) {
		ac.removeRevisionEndpoint(endpoint)
	}
	return true
}

func (ac *assignCache) removeEndpoint(endpoint string) bool {
	if endpoint == "" {
		return false
	}

	ac.Lock()
	defer ac.Unlock()

	token := ac.getRevisionEndpoint(endpoint)
	if token == "" {
		return false
	}
	info, ok := ac.tokenMap[token]
	if !ok {
		return false
	}
	if !info.removeEndpoint(endpoint) {
		return false
	}
	ac.tokenMap[token] = info
	ac.removeRevisionEndpoint(endpoint)
	return true
}

func (ac *assignCache) findTokenByEndpoint(endpoint string) string {
	ac.RLock()
	defer ac.RUnlock()

	return ac.getRevisionEndpoint(endpoint)
}

func (ac *assignCache) getSize() (token, endpoint int) {
	ac.RLock()
	defer ac.RUnlock()

	var c int
	for _, v := range ac.revisionEndpointMap {
		c += len(v)
	}

	return len(ac.tokenMap), c
}

func (ac *assignCache) walkRevisionEndpointMap(f func(revision string, endpoints map[string]string) bool) {
	ac.RLock()
	defer ac.RUnlock()

	for revision, endpoints := range ac.revisionEndpointMap {
		if !f(revision, endpoints) {
			return
		}
	}
}

// getActiveTokenInfo returns all active tokens info, iterate revisionEndpointMap is more efficient
func (ac *assignCache) getActiveTokenInfo() map[string]tidbInfo {
	ac.RLock()
	defer ac.RUnlock()

	tidbInfoMap := make(map[string]tidbInfo, 2000) //nolint:mnd // not need
	for _, endpoints := range ac.revisionEndpointMap {
		for _, token := range endpoints {
			if _, ok := tidbInfoMap[token]; !ok {
				tidbInfoMap[token] = ac.tokenMap[token]
			}
		}
	}
	return tidbInfoMap
}

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

	for k, v := range ac.tokenMap {
		item := struct {
			Token     string   `json:"token"`
			Endpoints []string `json:"endpoints"`
			Replicas  int      `json:"replicas"`
			Comment   string   `json:"comment"`
		}{
			Token:     k,
			Endpoints: v.endpoints,
			Replicas:  v.replicas,
			Comment:   v.comment,
		}
		data["tokens"] = append(data["tokens"], item)
	}
	for k, v := range ac.revisionEndpointMap {
		item := struct {
			Revision  string            `json:"revision"`
			Endpoints map[string]string `json:"endpoints"`
		}{
			Revision:  k,
			Endpoints: v,
		}
		data["revisionEndpoints"] = append(data["revisionEndpoints"], item)
	}
}
