package config

import (
	"fmt"
	"io"
	"os"

	"braces.dev/errtrace"
	"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/imdario/mergo"
	"gopkg.in/yaml.v3"
)

func NewConfig(path string) (*Config, error) {
	var (
		fileCfg, secretCfg *Config
		err                error
	)

	if fileCfg, err = openFile(path); err != nil {
		return nil, fmt.Errorf("failed to open file: %w", err)
	}
	if fileCfg.AwsSecret == nil || fileCfg.AwsSecret.Key == "" || fileCfg.AwsSecret.Region == "" {
		return fileCfg, nil
	}

	if secretCfg, err = getSecretFromAwsSecretManager(fileCfg.AwsSecret.Key, fileCfg.AwsSecret.Region, fileCfg.AwsSecret.AwsProfile); err != nil {
		return nil, fmt.Errorf("failed to get secret from aws secret manager: %w", err)
	}
	if err := mergeConfig(fileCfg, secretCfg); err != nil {
		return nil, fmt.Errorf("failed to merge config: %w", err)
	}

	if fileCfg.ServerRuntimeConfig.APIKey == "" {
		return nil, errtrace.New("failed to load dedicated api key")
	}

	return fileCfg, nil
}

func openFile(path string) (*Config, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, errtrace.Wrap(err)
	}
	defer file.Close()

	content, err := io.ReadAll(file)
	if err != nil {
		return nil, errtrace.Wrap(err)
	}
	return errtrace.Wrap2(parseConfig(content))
}

func parseConfig(content []byte) (*Config, error) {
	var config Config
	err := defaults.Set(&config)
	if err != nil {
		return nil, errtrace.Wrap(err)
	}
	err = yaml.Unmarshal(content, &config)
	if err != nil {
		return nil, errtrace.Wrap(err)
	}
	return &config, nil
}

func getSecretFromAwsSecretManager(secretName, region, profile string) (*Config, error) {
	// Read config and start session
	awsConfig := aws.NewConfig().WithMaxRetries(3).WithRegion(region)
	opts := session.Options{Config: *awsConfig, SharedConfigState: session.SharedConfigEnable, Profile: profile}
	sess, err := session.NewSessionWithOptions(opts)
	if err != nil {
		return nil, errtrace.Wrap(err)
	}

	svc := secretsmanager.New(sess)
	input := &secretsmanager.GetSecretValueInput{
		SecretId:     aws.String(secretName),
		VersionStage: aws.String("AWSCURRENT"),
	}
	result, err := svc.GetSecretValue(input)
	if err != nil {
		return nil, errtrace.Wrap(err)
	}

	return errtrace.Wrap2(parseConfig([]byte(*result.SecretString)))
}

func mergeConfig(dst, src *Config) error {
	if err := mergo.Merge(dst, src, mergo.WithOverride); err != nil {
		return errtrace.Wrap(err)
	}
	return nil
}
