package config

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"reflect"
	"sync"
	"time"

	"github.com/creasty/defaults"
	"github.com/go-playground/validator/v10"
	"github.com/mitchellh/mapstructure"
	"github.com/pingcap/log"
	"github.com/spf13/viper"
	"go.uber.org/zap"
)

const (
	defaultConfigPath   = "/etc/gateway/config.yaml"
	defaultPollInterval = 30 * time.Second
)

var (
	globalDynamicConf     = &DynamicConfig{}
	globalDynamicConfLock sync.Mutex
	dynamicConfigValidate = validator.New()
)

type DynamicConfig struct {
	ConnLimit ConnLimit `mapstructure:"conn_limit"`
}

type ConnLimit struct {
	Enable          bool             `mapstructure:"enable" default:"false"`
	FreeStarter     int64            `mapstructure:"free_starter" default:"400"`
	PaidStarter     int64            `mapstructure:"paid_starter" default:"5000"`
	ClusterSpecific map[string]int64 `mapstructure:"cluster_specific"`
}

// DynamicConfigWatcherConfig is the configuration for the dynamic config watcher.
type DynamicConfigWatcherConfig struct {
	ConfigPath   string
	PollInterval time.Duration
}

// DynamicConfigWatcher watches the dynamic config file and updates the global config.
type DynamicConfigWatcher struct {
	cfg *DynamicConfigWatcherConfig
}

// NewDynamicConfigWatcher creates a new DynamicConfigWatcher.
func NewDynamicConfigWatcher(cfg *DynamicConfigWatcherConfig) *DynamicConfigWatcher {
	if cfg.ConfigPath == "" {
		cfg.ConfigPath = defaultConfigPath
	}
	if cfg.PollInterval == 0 {
		cfg.PollInterval = defaultPollInterval
	}
	return &DynamicConfigWatcher{
		cfg: cfg,
	}
}

// Start starts the dynamic config watcher.
func (w *DynamicConfigWatcher) Start(ctx context.Context) {
	// Load initial config
	if err := w.loadConfig(); err != nil {
		log.Warn("failed to load initial dynamic config, using defaults", zap.Error(err))
	}

	ticker := time.NewTicker(w.cfg.PollInterval)
	defer ticker.Stop()

	log.Info("start watching dynamic config",
		zap.String("config_path", w.cfg.ConfigPath),
		zap.Duration("poll_interval", w.cfg.PollInterval))

	for {
		select {
		case <-ctx.Done():
			log.Info("stop watching dynamic config, context done")
			return
		case <-ticker.C:
			if err := w.loadConfig(); err != nil {
				log.Error("failed to load dynamic config, will retry later", zap.Error(err))
			}
		}
	}
}

func (w *DynamicConfigWatcher) loadConfig() error {
	data, err := os.ReadFile(w.cfg.ConfigPath)
	if err != nil {
		if os.IsNotExist(err) {
			log.Warn("dynamic config file not found, using defaults",
				zap.String("path", w.cfg.ConfigPath))
			return nil
		}
		return fmt.Errorf("failed to read config file: %w", err)
	}

	config := &DynamicConfig{}
	if err := normalizeDynamicConfig(config, string(data)); err != nil {
		log.Error("failed to normalize dynamic config", zap.Error(err))
		return err
	}

	globalDynamicConfLock.Lock()
	changed := !reflect.DeepEqual(globalDynamicConf, config)
	if changed {
		log.Info("dynamic config reloaded", zap.Any("config", config))
		globalDynamicConf = config
	}
	globalDynamicConfLock.Unlock()

	return nil
}

// normalizeDynamicConfigDefaults sets the default values for the dynamic config.
func normalizeDynamicConfigDefaults(config *DynamicConfig) error {
	return defaults.Set(config)
}

// normalizeDynamicConfigSetup unmarshals the config data using viper.
// It preserves existing values in the config if keys are missing in the data.
func normalizeDynamicConfigSetup(config *DynamicConfig, data string) error {
	v := viper.New()
	v.SetConfigType("yaml")
	if err := v.ReadConfig(bytes.NewBufferString(data)); err != nil {
		return fmt.Errorf("failed to read config: %w", err)
	}
	if err := v.Unmarshal(config, func(dc *mapstructure.DecoderConfig) {
		dc.ZeroFields = false // Don't overwrite existing values with zero values
	}); err != nil {
		return fmt.Errorf("failed to unmarshal config: %w", err)
	}
	return nil
}

// normalizeDynamicConfigValidate validates the dynamic config values.
func normalizeDynamicConfigValidate(config *DynamicConfig) error {
	if err := dynamicConfigValidate.Struct(config); err != nil {
		return fmt.Errorf("failed to validate config: %w", err)
	}
	return nil
}

// normalizeDynamicConfig applies defaults, unmarshals, and validates the dynamic config.
func normalizeDynamicConfig(config *DynamicConfig, data string) error {
	if err := normalizeDynamicConfigSetup(config, data); err != nil {
		return err
	}

	// Apply defaults after unmarshaling to fill in missing fields
	if err := normalizeDynamicConfigDefaults(config); err != nil {
		return err
	}

	if err := normalizeDynamicConfigValidate(config); err != nil {
		return err
	}

	return nil
}

// GlobalDynamicConfig returns the global dynamic config.
func GlobalDynamicConfig() *DynamicConfig {
	return globalDynamicConf
}
