package router

import (
	"fmt"
	"sync"
)

type Pod struct {
	TidbPool string `json:"tidb_pool"`
	IP       string `json:"ip"`
	Name     string `json:"name"`
}

type Backend struct {
	Pods        []*Pod `json:"pods"`
	TidbPool    string `json:"tidb_pool"`
	ModRevision int64  `json:"mod_revision"`
	Replicas    int    `json:"replicas"`
}

// Cache is gateway's cache of backend tidb addresses
type Cache struct {
	sync.RWMutex
	backends map[string]*Backend
}

func newCache() *Cache {
	return &Cache{backends: make(map[string]*Backend)}
}

func (c *Cache) listAllPods() []*Pod {
	c.RLock()
	defer c.RUnlock()

	pods := make([]*Pod, 0)
	for _, be := range c.backends {
		pods = append(pods, be.Pods...)
	}
	return pods
}

func (c *Cache) getPoolBackend(tidbPool string) ([]*Pod, int, bool) {
	c.RLock()
	defer c.RUnlock()

	if backend, ok := c.backends[tidbPool]; ok {
		pods := make([]*Pod, len(backend.Pods))
		copy(pods, backend.Pods)
		return pods, backend.Replicas, backend.Replicas > 0
	}
	return nil, 0, false
}

func samePods(left, right []*Pod) bool {
	if len(left) != len(right) {
		return false
	}
	for i := range left {
		if left[i] == nil || right[i] == nil {
			if left[i] != right[i] {
				return false
			}
			continue
		}
		if *left[i] != *right[i] {
			return false
		}
	}
	return true
}

func (c *Cache) set(backend *Backend) error {
	c.Lock()
	defer c.Unlock()

	existBackend := c.backends[backend.TidbPool]

	// backend modRevision == 0 indicates the key is deleted. We should not skip it.
	if existBackend != nil && backend.ModRevision != 0 {
		if existBackend.ModRevision > backend.ModRevision {
			return fmt.Errorf(
				"stale mod revision: exist: %d, income: %d",
				existBackend.ModRevision,
				backend.ModRevision,
			)
		}
		if existBackend.ModRevision == backend.ModRevision {
			// Allow a new manager to enrich an old manager's endpoint snapshot.
			if existBackend.Replicas == 0 && backend.Replicas > 0 && samePods(existBackend.Pods, backend.Pods) {
				enrichedBackend := *existBackend
				enrichedBackend.Replicas = backend.Replicas
				c.backends[backend.TidbPool] = &enrichedBackend
				return nil
			}
			return fmt.Errorf(
				"stale mod revision: exist: %d, income: %d",
				existBackend.ModRevision,
				backend.ModRevision,
			)
		}
	}
	if existBackend == nil && len(backend.Pods) == 0 && backend.Replicas == 0 {
		return fmt.Errorf("no need to update empty backend")
	}

	if len(backend.Pods) == 0 && backend.Replicas == 0 {
		delete(c.backends, backend.TidbPool)
	} else {
		c.backends[backend.TidbPool] = backend
	}
	return nil
}

func (c *Cache) Clean() {
	c.Lock()
	defer c.Unlock()

	c.backends = make(map[string]*Backend)
}
