package core

import (
	"context"
	"github.com/guo-shaoge/tiflash-autoscale/pkg/config"
	"github.com/guo-shaoge/tiflash-autoscale/pkg/util/log/logger"
	"go.uber.org/zap"
	"sync"
	"time"
)

type ConfigMgr struct {
	ctx        context.Context
	configPath string
	logger     *zap.Logger

	mu struct {
		sync.RWMutex
		config *config.Config
	}
}

// NewConfigMgr return new config mgr, will panic if got error
func NewConfigMgr(ctx context.Context, configPath string) *ConfigMgr {
	res := &ConfigMgr{
		ctx:        ctx,
		configPath: configPath,
	}
	res.mu.config = config.NewConfig(configPath)
	return res
}

func (c *ConfigMgr) SetupLogger(parentLogger *zap.Logger) {
	c.logger = parentLogger.With(zap.String(logger.LogIDMod, "configMgr"))
}

func (c *ConfigMgr) LogConfig() {
	config := c.GetConfig()
	configStr, err := config.ToString()
	c.logger.Info("config", zap.Error(err), zap.String("config", configStr))
}

func (c *ConfigMgr) startBgWorker(wg *sync.WaitGroup) {
	wg.Add(1)
	go func() {
		c.checkConfigLoop()
	}()
}

func (c *ConfigMgr) HasNewerConfig(oriConfig *config.Config) bool {
	curConfig := c.GetConfig()
	return curConfig.GetUpdateTime().After(oriConfig.GetUpdateTime())
}

func (c *ConfigMgr) GetConfig() *config.Config {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.mu.config
}

func (c *ConfigMgr) checkConfigLoop() {
	for {
		interval := *c.GetConfig().GetClusterConfig().ConfigCheckIntervalSec
		ticker := time.NewTicker(time.Duration(interval) * time.Second)

		select {
		case <-ticker.C:
			var newC *config.Config

			func() {
				defer func() {
					if r := recover(); r != nil {
						c.logger.Error("load new config failed", zap.Any("r", r))
						newC = nil
					} else if newC == nil {
						c.logger.Error("load new config failed, newConfig is nil")
						newC = nil
					}
				}()
				newC = config.NewConfig(c.configPath)
			}()

			if newC == nil {
				break
			}

			oriConfigVer := *c.GetConfig().GetClusterConfig().ConfigVersion
			if *newC.GetClusterConfig().ConfigVersion > oriConfigVer {
				str, err := newC.ToString()
				if err != nil {
					c.logger.Error("config updated failed, fail to parse new config to json", zap.Error(err))
					break
				}
				c.logger.Info("config updated succeed", zap.String("config", str))
				c.updateConfig(newC)
			}
		case <-c.ctx.Done():
			c.logger.Info("got ctx.Done(), exit")
			break
		}
	}
}

func (c *ConfigMgr) updateConfig(newC *config.Config) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.mu.config = newC
}
