package config

import (
	"context"
	"errors"
	"fmt"
	"time"

	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/go-sql-driver/mysql"
	"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 <- local config (config file) <- remote config <- secrets
// defaults for tag default in the config struct.

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

// InitConfig parse the central configuration file and init the global config var
func InitConfig(path string) (*Config, error) {
	viper.SetConfigFile(path)
	viper.AutomaticEnv() // for M2M env variables

	if err := viper.ReadInConfig(); err != nil {
		return nil, err
	}

	c := &Config{}

	if err := defaults.Set(c); err != nil {
		return nil, err
	}

	if err := mergeMultipleConfigs(c, loadRemoteConfig); err != nil {
		return nil, err
	}

	if err := validate.Struct(c); err != nil {
		return nil, fmt.Errorf("failed to validate the config values: %w", err)
	}

	if c.BizConfig.ClusterQuota.TiDB == 0 || c.BizConfig.ClusterQuota.TiKV == 0 ||
		c.BizConfig.ClusterQuota.TiFlash == 0 {
		return nil, errors.New("init quota failed")
	}
	return c, nil
}

func GetMysqlDefaultConfig(cfg *Config) *mysql.Config {
	mysqlCfg := cfg.MysqlConfig

	defaultConfig := mysql.NewConfig()

	defaultConfig.Net = "tcp"
	defaultConfig.User = mysqlCfg.User
	defaultConfig.Addr = fmt.Sprintf("%s:%d", mysqlCfg.Host, mysqlCfg.Port)
	defaultConfig.Passwd = mysqlCfg.Password
	defaultConfig.DBName = mysqlCfg.Database
	defaultConfig.ParseTime = true
	defaultConfig.Timeout = 60 * time.Second
	defaultConfig.ReadTimeout = 30 * time.Second
	defaultConfig.WriteTimeout = 30 * time.Second
	defaultConfig.Params = map[string]string{"charset": "utf8mb4"}
	defaultConfig.Collation = "utf8mb4_bin"
	defaultConfig.TLSConfig = mysqlCfg.TLSConfigKey

	return defaultConfig
}

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

func mergeMultipleConfigs(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,
			config.CoreSvcSecretStore.Profile,
			config.CoreSvcSecretStore.RetryCount,
		)
		if err != nil {
			return err
		}
		config.ProxyConfig.Auth0ClientSecret = s.ProxyConfig.Auth0ClientSecret

		tilog.Info(context.Background(), "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,
			config.ServerConfig.SecretStore.Profile,
			config.ServerConfig.SecretStore.RetryCount,
		)
		if err != nil {
			return err
		}

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

	return nil
}

func loadRemoteConfig(smArn, region, profile string, retry int) (*Config, error) {
	secretConfig, err := getSecretFromAwsSecretManager(smArn, region, profile, retry)
	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 getSecretFromAwsSecretManager(secretName, _, _ string, retryCount int) (string, error) {
	awsConfig, err := awsconfig.LoadDefaultConfig(context.TODO())
	if err != nil {
		panic("AWS configuration error, " + err.Error())
	}
	awsConfig.RetryMaxAttempts = retryCount
	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(context.TODO(), input)
	if err != nil {
		return "", err
	}
	return *result.SecretString, nil
}
