//go:generate ${GOBIN}/dummygen -d=client_dummy.go -package=awscli ${GO_MODULE}/pkg/infra/clients/awscli AWSClient
package awscli

import (
	"context"
	"encoding/base64"
	"fmt"

	"github.com/aws/aws-sdk-go-v2/aws"
	awsconfig "github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/eks"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
	"github.com/aws/aws-sdk-go-v2/service/sts"
	smithyhttp "github.com/aws/smithy-go/transport/http"
	"github.com/tidbcloud/devops/pkg/infra/config"
)

type AWSClient interface {
	ASM() *secretsmanager.Client
	S3() *s3.Client
	EKS() *eks.Client
}

type awsClient struct {
	conf aws.Config

	// ready to use clients
	asm *secretsmanager.Client
	s3  *s3.Client
	eks *eks.Client
}

func NewAWSClient(conf *config.RootConfig) (AWSClient, error) {
	if conf.Clients.AWS.Dummy {
		return &DummyAWSClient{}, nil
	}

	ctx := context.Background()
	cli := &awsClient{}

	awsConf, err := awsconfig.LoadDefaultConfig(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to load aws config: %w", err)
	}
	cli.conf = awsConf

	cli.asm = secretsmanager.NewFromConfig(awsConf)
	cli.s3 = s3.NewFromConfig(awsConf)
	cli.eks = eks.NewFromConfig(awsConf)

	return cli, nil
}

func (c *awsClient) ASM() *secretsmanager.Client {
	return c.asm
}

func (c *awsClient) S3() *s3.Client {
	return c.s3
}

func (c *awsClient) EKS() *eks.Client {
	return c.eks
}

// GetWithSTS returns a token valid for clusterID using the given STS client.
//
// Ref: https://github.com/kubernetes-sigs/aws-iam-authenticator#api-authorization-from-outside-a-cluster
//
// Copy from eksctl, see https://github.com/eksctl-io/eksctl/blob/d20b48e70a782324d748e392de16cd03dc50d44c/pkg/eks/auth/generator.go#L51
func GenerateEKSTokenWithSTS(ctx context.Context, stsCli *sts.Client, eksname string) (string, error) {
	presignCli := sts.NewPresignClient(stsCli)
	req, err := presignCli.PresignGetCallerIdentity(ctx, &sts.GetCallerIdentityInput{}, func(po *sts.PresignOptions) {
		po.ClientOptions = append(po.ClientOptions, appendPresignHeaderValuesFunc(eksname))
	})
	if err != nil {
		return "", fmt.Errorf("failed to presign caller identity: %w", err)
	}

	// Add the token with k8s-aws-v1. prefix.
	token := "k8s-aws-v1." + base64.RawURLEncoding.EncodeToString([]byte(req.URL))
	return token, nil
}

func appendPresignHeaderValuesFunc(eksname string) func(stsOptions *sts.Options) {
	return func(stsOptions *sts.Options) {
		// Add clusterId Header
		stsOptions.APIOptions = append(stsOptions.APIOptions, smithyhttp.SetHeaderValue("x-k8s-aws-id", eksname))
		// Add X-Amz-Expires query param
		stsOptions.APIOptions = append(stsOptions.APIOptions, smithyhttp.SetHeaderValue("X-Amz-Expires", "60"))
	}
}
