package dashboard

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/devops/pkg/infra/clients/cliniccli"
)

type ClusterCacheType string

const (
	CacheTypeDedicated ClusterCacheType = "dedicated"
	CacheTypeNextGen   ClusterCacheType = "next-gen"
)

type ClusterCache struct {
	cacheType    ClusterCacheType
	syncInterval *time.Duration
	clinicClient cliniccli.Client

	logger logr.Logger
	cache  map[string]cliniccli.Cluster
	lock   sync.Mutex
	once   sync.Once
}

func NewClusterCache(
	cacheType ClusterCacheType,
	logger logr.Logger,
	syncInterval *time.Duration,
	clinicClient cliniccli.Client,
) *ClusterCache {
	var cacheName string
	switch cacheType {
	case CacheTypeDedicated:
		cacheName = "DedicatedClusterCache"
	case CacheTypeNextGen:
		cacheName = "NextGenClusterCache"
	default:
		cacheName = "ClusterCache"
	}

	return &ClusterCache{
		cacheType:    cacheType,
		logger:       logger.WithName(cacheName),
		syncInterval: syncInterval,
		cache:        make(map[string]cliniccli.Cluster),
		clinicClient: clinicClient,
	}
}

func (c *ClusterCache) Get() (map[string]cliniccli.Cluster, error) {
	if len(c.cache) > 0 {
		return c.cache, nil
	}

	c.lock.Lock()
	defer c.lock.Unlock()
	if len(c.cache) > 0 {
		return c.cache, nil
	}

	cache, err := c.sync()
	if err != nil {
		return nil, err
	}
	c.cache = cache
	c.once.Do(func() {
		go c.syncScheduler()
	})
	return c.cache, nil
}

func (c *ClusterCache) syncScheduler() {
	syncTicker := time.NewTicker(*c.syncInterval)
	for {
		<-syncTicker.C
		c.lock.Lock()
		cache, err := c.sync()
		if err != nil {
			c.logger.Error(err, "sync failed")
			c.lock.Unlock()
			continue
		}
		c.cache = cache
		c.lock.Unlock()
	}
}

func (c *ClusterCache) sync() (map[string]cliniccli.Cluster, error) {
	c.logger.Info("syncing cluster cache", "type", c.cacheType)

	var deployTypes []string
	switch c.cacheType {
	case CacheTypeDedicated:
		deployTypes = []string{cliniccli.DeployTypeDedicated}
	case CacheTypeNextGen:
		deployTypes = []string{cliniccli.DeployTypePremium, cliniccli.DeployTypeByoc}
	}

	aliveClusters, err := c.clinicClient.ListClusters(
		context.Background(),
		&cliniccli.ClusterFilter{
			DeployType:  deployTypes,
			ShowDeleted: false,
		})
	if err != nil {
		return nil, fmt.Errorf("get alive clusters from clinic failed: %w", err)
	}

	aliveClusterMap := make(map[string]cliniccli.Cluster)
	for _, cluster := range aliveClusters {
		aliveClusterMap[cluster.ClusterID] = cluster
	}

	c.logger.Info("sync done", "clusterCount", len(aliveClusterMap), "type", c.cacheType)
	return aliveClusterMap, nil
}

func newDedicatedClusterCache(
	logger logr.Logger,
	syncInterval *time.Duration,
	clinicClient cliniccli.Client,
) *ClusterCache {
	return NewClusterCache(CacheTypeDedicated, logger, syncInterval, clinicClient)
}

func newNextGenClusterCache(
	logger logr.Logger,
	syncInterval *time.Duration,
	clinicClient cliniccli.Client,
) *ClusterCache {
	return NewClusterCache(CacheTypeNextGen, logger, syncInterval, clinicClient)
}
