package config

import (
	"os"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/secretsmanager"
	"github.com/creasty/defaults"
	"github.com/go-playground/validator/v10"
	"github.com/imdario/mergo"
	"github.com/mitchellh/mapstructure"
	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"github.com/spf13/viper"
	ov "github.com/tidbcloud/config-service/oneconfig/viper"
	"go.uber.org/zap"
	"gopkg.in/yaml.v2"
)

// @Time    : 2022/11/14 4:56 PM
// @Author  : lvhongmeng

var validate = validator.New()

func Global() *CentralConfig {
	return globalConf
}

func LoadRemote() error {
	//remote := "nightly/oneconfig-dataservices/config.yaml" // Local mode use this path
	remote := "oneconfig-dataservices/config.yaml"
	if viper.GetBool("server.localMode") {
		remote = "nightly-ms/oneconfig-dataservices/config.yaml"
	}
	log.Info("Read From Remote", zap.String("remote", remote))
	err := viper.AddRemoteProvider(ov.Provider, ov.Endpoint, remote)
	if err != nil {
		return err
	}

	err = viper.WatchRemoteConfig()
	if err != nil {
		return err
	}

	err = viper.GetViper().WatchRemoteConfigOnChannel()
	if err != nil {
		return err
	}
	return nil
}

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

	if viper.GetString("server.deployMode") == "central" {
		err = LoadRemote()
		if err != nil {
			log.Warn("load remote config failed, just ignore it in persnonal project", zap.Error(err))
		}
	}
	err = NormalizeConfig()
	if err != nil {
		return err
	}

	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
	}
	globalConf = config

	return nil
}

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")
	}
	if config.IsRegionMode() {
		config.RedisConf.Password = os.Getenv("REDIS_SRV_PASS")
		config.LogQueryRedisConf.Password = os.Getenv("REDIS_SRV_PASS")
		config.ServerConf.CrossRegionToken = os.Getenv("CROSS_REGION_TOKEN")
	} else {
		// let's load some secret from aws and merge it
		if config.ServerConf.SecretStore != nil {
			secretConfig, err := GetSecretFromAwsSecretManager(config.ServerConf.SecretStore.SecretKey, config.ServerConf.SecretStore.Region)
			if err != nil {
				return errors.Annotate(err, "failed to load secret config from aws")
			}
			m := make(map[interface{}]interface{})
			if err := yaml.Unmarshal([]byte(secretConfig), &m); err != nil {
				return errors.Annotate(err, "failed to unmarshal secret config")
			}

			var s CentralConfig
			if err := mapstructure.Decode(m, &s); err != nil {
				return errors.Annotate(err, "failed to mapsturcture secret config")
			}
			if err := mergo.Merge(config, s, mergo.WithOverride); err != nil {
				return errors.Annotate(err, "failed to merge secret config")
			}
		}
	}
	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 GetSecretFromAwsSecretManager(secretName, region string) (string, error) {
	awsConfig := aws.NewConfig().WithMaxRetries(3).WithRegion(region)
	opts := session.Options{Config: *awsConfig}
	sess, err := session.NewSessionWithOptions(opts)
	if err != nil {
		return "", err
	}
	svc := secretsmanager.New(sess)
	input := &secretsmanager.GetSecretValueInput{
		SecretId:     aws.String(secretName),
		VersionStage: aws.String("AWSCURRENT"), // VersionStage defaults to AWSCURRENT if unspecified
	}
	result, err := svc.GetSecretValue(input)
	if err != nil {
		return "", err
	}
	return *result.SecretString, nil
}
