package config

import (
	"strings"

	"github.com/creasty/defaults"
	"github.com/fsnotify/fsnotify"
	"github.com/go-playground/validator/v10"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"github.com/spf13/viper"
	"github.com/tidbcloud/metrics-integration/pipeline/central/entity"
	"go.uber.org/zap"
)

// use defaults <- pulumi config (config file) <- remote config <- secrets
// defaults for tag default in the config struct.

// use a single instance of Validate, it caches struct info
var validate = validator.New() // nolint

func NormalizeConfigDefaults(config *CentralConfig) error {
	return defaults.Set(config)
}

func NormalizeConfigSetup(config *CentralConfig) error {
	err := viper.Unmarshal(config)
	if err != nil {
		return errors.Annotate(err, "failed unmarshal from viper")
	}

	return nil
}

func NormalizeConfigValidate(config *CentralConfig) error {
	// validate the values
	if err := validate.Struct(config); err != nil {
		return errors.Annotate(err, "failed to validate the config values")
	}

	return nil
}

func NormalizeConfig() error {
	config := &CentralConfig{}

	err := NormalizeConfigDefaults(config)
	if err != nil {
		return err
	}
	err = NormalizeConfigSetup(config)
	if err != nil {
		return err
	}
	err = NormalizeConfigValidate(config)
	if err != nil {
		return err
	}

	for i := range config.TargetClusters {
		if err := NormalizeTargetCluster(&config.TargetClusters[i]); err != nil {
			return errors.Annotatef(err, "failed to normalize targetClusters[%d] (id=%d)", i, config.TargetClusters[i].Id)
		}
	}

	globalConf = config
	return nil
}

// InitConfig parse the central configuration file and init the global config var
func InitConfig(path string) error {
	viper.SetConfigFile(path)
	err := viper.ReadInConfig()
	if err != nil {
		return err
	}

	// register config change watcher
	viper.WatchConfig()
	viper.OnConfigChange(func(e fsnotify.Event) {
		log.Info("config file changed", zap.String("path", e.Name))
		// reload and re-normalize config when file changes
		if err := NormalizeConfig(); err != nil {
			log.Error("failed to reload config after file change", zap.Error(err))
		} else {
			log.Info("config successfully reloaded")
		}
	})
	err = NormalizeConfig()
	if err != nil {
		return err
	}

	return nil
}

func Global() *CentralConfig {
	return globalConf
}

func NormalizeTargetCluster(targetCluster *TargetCluster) error {
	normalized, err := entity.NormalizeServicePlan(targetCluster.ServicePlan)
	if err != nil {
		return err
	}
	targetCluster.ServicePlan = string(normalized)
	targetCluster.Components = normalizeClusterComponents(targetCluster.Components)
	return nil
}

func normalizeTargetClusterServicePlans(targetClusters TargetClusters) error {
	for i := range targetClusters {
		normalized, err := entity.NormalizeServicePlan(targetClusters[i].ServicePlan)
		if err != nil {
			return errors.Annotatef(err, "failed to normalize servicePlan for targetClusters[%d] (id=%d)", i, targetClusters[i].Id)
		}
		targetClusters[i].ServicePlan = string(normalized)
	}

	return nil
}

func normalizeTargetClusterComponents(targetClusters TargetClusters) {
	for i := range targetClusters {
		targetClusters[i].Components = normalizeClusterComponents(targetClusters[i].Components)
	}
}

func normalizeClusterComponents(components []ClusterComponent) []ClusterComponent {
	filtered := make([]ClusterComponent, 0, len(components))
	for _, component := range components {
		componentType, ok := normalizeTargetClusterComponentType(component.ComponentType)
		if !ok {
			// Intentionally ignore unsupported component types here. Configs can arrive
			// from a newer control plane before pipeline knows that component; keeping
			// the unknown entry makes later metrics paths panic or emit invalid config.
			// Keep this silent drop for forward compatibility.
			continue
		}
		component.ComponentType = componentType
		filtered = append(filtered, component)
	}
	return filtered
}

func normalizeTargetClusterComponentType(componentType string) (string, bool) {
	switch strings.ToUpper(strings.TrimSpace(componentType)) {
	case "COMPONENT_TYPE_TIDB":
		return "COMPONENT_TYPE_TIDB", true
	case "COMPONENT_TYPE_PD":
		return "COMPONENT_TYPE_PD", true
	case "COMPONENT_TYPE_TIKV":
		return "COMPONENT_TYPE_TIKV", true
	case "COMPONENT_TYPE_TIFLASH":
		return "COMPONENT_TYPE_TIFLASH", true
	default:
		return "", false
	}
}
