package secrets_manager

import (
	"encoding/base64"
	credentials2 "github.com/aws/aws-sdk-go/aws/credentials"
	"os"

	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs"
	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/struct_utils"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/secretsmanager"
	"github.com/aws/aws-secretsmanager-caching-go/secretcache"
)

type DatabaseCredentials struct {
	Username           string `json:"username"`
	Password           string `json:"password"`
	Engine             string `json:"engine"`
	Host               string `json:"host"`
	Port               int    `json:"port"`
	InstanceIdentifier string `json:"dbInstanceIdentifier"`
	ReadOnlyHost       string `json:"aurora_read_only_host"`
}

type S3UploadCredentials struct {
	AccessKeyID string `json:"accessKeyID"`
	SecretKey   string `json:"secretKey"`
}

var (
	secretCache, _      = secretcache.New()
	secretManagerRegion = "af-south-1"
)

func GetDatabaseCredentials(secretID string, isDebug bool) (DatabaseCredentials, error) {
	secret, _ := GetSecret(secretID, isDebug)
	var credentials DatabaseCredentials
	err := struct_utils.UnmarshalJSON([]byte(secret), &credentials)
	if err != nil {
		return DatabaseCredentials{}, err
	}
	return credentials, nil
}

func GetS3UploadCredentials(secretID string, isDebug bool) (*credentials2.Credentials, error) {
	secret, _ := GetSecret(secretID, isDebug)
	var credentials S3UploadCredentials
	err := struct_utils.UnmarshalJSON([]byte(secret), &credentials)
	if err != nil {
		return nil, err
	}
	return credentials2.NewStaticCredentials(credentials.AccessKeyID, credentials.SecretKey, ""), nil
}

func GetSecret(secretID string, isDebug bool) (string, string) {
	cachedSecret, err := secretCache.GetSecretString(secretID)
	if err != nil {
		logs.Info("Failed to get secret key from cache")
	}
	if cachedSecret != "" {
		return cachedSecret, ""
	}

	awsSession := session.New()

	// Get local config
	if isDebug && os.Getenv("ENVIRONMENT") != "" {
		logs.Info("Using access key %s", os.Getenv("AWS_ACCESS_KEY_ID"))
		awsSession, err = session.NewSessionWithOptions(session.Options{
			Config: aws.Config{
				Region:                        aws.String("af-south-1"),
				CredentialsChainVerboseErrors: aws.Bool(true),
			},
		})
		if err != nil {
			return "", ""
		}
	}

	// Create a Secrets Manager client
	svc := secretsmanager.New(awsSession, aws.NewConfig().WithRegion(secretManagerRegion))

	input := &secretsmanager.GetSecretValueInput{
		SecretId:     aws.String(string(secretID)),
		VersionStage: aws.String("AWSCURRENT"), // VersionStage defaults to AWSCURRENT if unspecified
	}

	// In this sample we only handle the specific exceptions for the 'GetSecretValue' API.
	// See https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html

	result, err := svc.GetSecretValue(input)
	if err != nil {
		if aerr, ok := err.(awserr.Error); ok {
			switch aerr.Code() {
			case secretsmanager.ErrCodeDecryptionFailure:
				// Secrets Manager can't decrypt the protected secret text using the provided KMS key.
				logs.Info(secretsmanager.ErrCodeDecryptionFailure, aerr.Error())

			case secretsmanager.ErrCodeInternalServiceError:
				// An error occurred on the server side.
				logs.Info(secretsmanager.ErrCodeInternalServiceError, aerr.Error())

			case secretsmanager.ErrCodeInvalidParameterException:
				// You provided an invalid value for a parameter.
				logs.Info(secretsmanager.ErrCodeInvalidParameterException, aerr.Error())

			case secretsmanager.ErrCodeInvalidRequestException:
				// You provided a parameter value that is not valid for the current state of the resource.
				logs.Info(secretsmanager.ErrCodeInvalidRequestException, aerr.Error())

			case secretsmanager.ErrCodeResourceNotFoundException:
				// We can't find the resource that you asked for.
				logs.Info("Can't find secret with ID: ", secretID)
				logs.Info(secretsmanager.ErrCodeResourceNotFoundException, aerr.Error())
			default:
				logs.Info(err.Error())
			}
		} else {
			// Print the error, cast err to awserr.Error to get the Code and
			// Message from an error.
			logs.Info(err.Error())
		}
		return "", ""
	}

	// Decrypts secret using the associated KMS CMK.
	// Depending on whether the secret is a string or binary, one of these fields will be populated.
	var secretString, decodedBinarySecret string
	if result.SecretString != nil {
		secretString = *result.SecretString
	} else {
		decodedBinarySecretBytes := make([]byte, base64.StdEncoding.DecodedLen(len(result.SecretBinary)))
		length, err := base64.StdEncoding.Decode(decodedBinarySecretBytes, result.SecretBinary)
		if err != nil {
			logs.Info("Base64 Decode Error:", err)
			return "", ""
		}
		decodedBinarySecret = string(decodedBinarySecretBytes[:length])
	}

	return secretString, decodedBinarySecret
}