package config

import (
	"context"
	"fmt"
	"sync"

	"braces.dev/errtrace"
	awsconfig "github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
	"github.com/creasty/defaults"
	"github.com/go-playground/validator/v10"
	"github.com/imdario/mergo"
	"github.com/mitchellh/mapstructure"
	"github.com/spf13/viper"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gopkg.in/yaml.v2"
	"k8s.io/utils/pointer"
)

// 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 *Config) error {
	return errtrace.Wrap(defaults.Set(config))
}

type loadConfigFunc func(string, string) (*Config, error)

func NormalizeConfigSetup(config *Config, loadConfig loadConfigFunc) error {
	err := viper.Unmarshal(config)
	if err != nil {
		return fmt.Errorf("failed unmarshal from viper: %w", err)
	}

	if config.CoreSvcSecretStore != nil {
		s, err := loadConfig(config.CoreSvcSecretStore.SecretKey, config.CoreSvcSecretStore.Region)
		if err != nil {
			return errtrace.Wrap(err)
		}
		config.ProxyConfig.Auth0ClientSecret = s.ProxyConfig.Auth0ClientSecret

		tilog.PlainInfo("reset auth secret", zap.Int("len", len(config.ProxyConfig.Auth0ClientSecret)))
	}

	if config.ServerConfig.SecretStore != nil {
		s, err := loadConfig(config.ServerConfig.SecretStore.SecretKey, config.ServerConfig.SecretStore.Region)
		if err != nil {
			return errtrace.Wrap(err)
		}

		if err := mergo.Merge(config, s, mergo.WithOverride); err != nil {
			return fmt.Errorf("failed to merge secret config: %w", err)
		}
	}

	return nil
}

func smConfig(smArn, region string) (*Config, error) {
	secretConfig, err := GetSecretFromAwsSecretManager(context.TODO(), smArn, region)
	if err != nil {
		return nil, fmt.Errorf("failed to load secret config from aws: %w", err)
	}
	m := make(map[any]any)
	if err := yaml.Unmarshal([]byte(secretConfig), &m); err != nil {
		return nil, fmt.Errorf("failed to unmarshal secret config: %w", err)
	}

	var s Config

	if err := mapstructure.Decode(m, &s); err != nil {
		return nil, fmt.Errorf("failed to mapsturcture secret config: %w", err)
	}

	return &s, nil
}

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

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

	err := NormalizeConfigDefaults(config)
	if err != nil {
		return errtrace.Wrap(err)
	}
	err = NormalizeConfigSetup(config, smConfig)
	if err != nil {
		return errtrace.Wrap(err)
	}
	err = NormalizeConfigValidate(config)
	if err != nil {
		return errtrace.Wrap(err)
	}
	globalConf = config
	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 errtrace.Wrap(err)
	}

	err = NormalizeConfig()
	if err != nil {
		return errtrace.Wrap(err)
	}

	return nil
}

func Global() *Config {
	return globalConf
}

var (
	secretsManagerCli  *secretsmanager.Client
	secretsManagerOnce sync.Once
)

func GetSecretFromAwsSecretManager(ctx context.Context, secretName, region string) (string, error) {
	secretsManagerOnce.Do(func() {
		awsConfig, err := awsconfig.LoadDefaultConfig(ctx)
		if err != nil {
			panic("AWS configuration error, " + err.Error())
		}
		awsConfig.RetryMaxAttempts = 3
		secretsManagerCli = secretsmanager.NewFromConfig(awsConfig)
	})

	input := &secretsmanager.GetSecretValueInput{
		SecretId:     pointer.String(secretName),
		VersionStage: pointer.String("AWSCURRENT"), // VersionStage defaults to AWSCURRENT if unspecified
	}
	result, err := secretsManagerCli.GetSecretValue(ctx, input)
	if err != nil {
		return "", errtrace.Wrap(err)
	}
	return *result.SecretString, nil
}
