package config

import (
	"fmt"

	"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"
	"go.uber.org/zap"
	"gopkg.in/yaml.v2"
)

// 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()

func normalizeConfigDefaults(config *EcosystemConfig) error {
	return defaults.Set(config)
}

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

	// let's load some secret from aws and merge it
	if config.ServerConfig.SecretStore != nil {
		secretConfig, err := getSecretFromAWSSecretManager(
			config.ServerConfig.SecretStore.SecretName, config.ServerConfig.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 EcosystemConfig

		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 *EcosystemConfig) 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(validate bool) (*EcosystemConfig, error) {
	config := &EcosystemConfig{}

	err := normalizeConfigDefaults(config)
	if err != nil {
		return nil, err
	}
	err = normalizeConfigSetup(config)
	if err != nil {
		return nil, err
	}
	if validate {
		err = normalizeConfigValidate(config)
		if err != nil {
			return nil, err
		}
	}

	return config, nil
}

// InitConfig parse the central configuration file and init the config
func InitConfig(path string) (*ConfigWrapper, error) {
	viper.SetConfigFile(path)
	viper.AutomaticEnv()
	err := viper.ReadInConfig()
	if err != nil {
		log.Error("ReadInConfig error", zap.Error(err))
		return nil, err
	}

	wrapper := &ConfigWrapper{}
	conf, err := normalizeConfig(true)
	if err != nil {
		log.Error("normalizeConfig error", zap.Error(err))
		return nil, err
	}

	log.Debug(fmt.Sprintf("config: %#v", conf))
	wrapper.EcosystemConfig = conf
	return wrapper, nil
}

func getSecretFromAWSSecretManager(secretName string, 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
}
