package alicloud

import (
	"fmt"

	openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
	privatelink "github.com/alibabacloud-go/privatelink-20200415/v5/client"
	sts "github.com/alibabacloud-go/sts-20150401/v2/client"
	vpc "github.com/alibabacloud-go/vpc-20160428/v6/client"
	"github.com/aliyun/credentials-go/credentials"
	"k8s.io/utils/ptr"
)

type Option func(*openapi.Config)

func WithRegion(region string) Option {
	return func(cfg *openapi.Config) {
		cfg.SetRegionId(region)
	}
}

// AssumedRole specifies the assumed role for issuing API requests.
type AssumedRole struct {
	RoleARN    string
	ExternalID *string
}

type Client interface {
	PrivateLink(*AssumedRole, ...Option) (*privatelink.Client, error)
	VPC(*AssumedRole, ...Option) (*vpc.Client, error)
}

type clientSet struct{}

func NewClientSet() Client {
	return &clientSet{}
}

func (c *clientSet) PrivateLink(ar *AssumedRole, opts ...Option) (*privatelink.Client, error) {
	cfg, err := buildConfig(ar, opts...)
	if err != nil {
		return nil, err
	}
	if cfg.GetRegionId() != nil {
		cfg.SetEndpoint(fmt.Sprintf("privatelink-vpc.%s.aliyuncs.com", *cfg.GetRegionId()))
	}
	return privatelink.NewClient(cfg)
}

func (c *clientSet) VPC(ar *AssumedRole, opts ...Option) (*vpc.Client, error) {
	cfg, err := buildConfig(ar, opts...)
	if err != nil {
		return nil, err
	}
	return vpc.NewClient(cfg)
}

func buildConfig(ar *AssumedRole, opts ...Option) (*openapi.Config, error) {
	config, err := GetDefaultCredentialConfig(opts...)
	if err != nil {
		return nil, err
	}
	// No assumed role provided, return the default credential provider config
	if ar == nil || ar.RoleARN == "" {
		return config, nil
	}
	return getStsCredentialConfig(config, ar, opts...)
}

func getStsCredentialConfig(config *openapi.Config, ar *AssumedRole, opts ...Option) (*openapi.Config, error) {
	// Use STS to assume role and retrieve assumed role credentials
	stsClient, err := sts.NewClient(config)
	if err != nil {
		return nil, err
	}
	req := &sts.AssumeRoleRequest{
		RoleArn:         ptr.To(ar.RoleARN),
		ExternalId:      ar.ExternalID,
		RoleSessionName: ptr.To("agent-control-server"),
	}
	resp, err := stsClient.AssumeRole(req)
	if err != nil {
		return nil, err
	}
	cfg := new(openapi.Config).
		SetAccessKeyId(*resp.Body.Credentials.AccessKeyId).
		SetAccessKeySecret(*resp.Body.Credentials.AccessKeySecret).
		SetSecurityToken(*resp.Body.Credentials.SecurityToken)
	for _, opt := range opts {
		opt(cfg)
	}
	return cfg, nil
}

func GetDefaultCredentialConfig(opts ...Option) (*openapi.Config, error) {
	provider, err := credentials.NewCredential(nil) // use default credential provider chain
	if err != nil {
		return nil, err
	}
	cred, err := provider.GetCredential()
	if err != nil {
		return nil, err
	}
	config := new(openapi.Config).
		SetAccessKeyId(*cred.AccessKeyId).
		SetAccessKeySecret(*cred.AccessKeySecret).
		SetSecurityToken(*cred.SecurityToken)
	for _, opt := range opts {
		opt(config)
	}
	return config, nil
}
