package obs

import (
	"fmt"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/factory/reg"
	s3stg "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/s3"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
	"gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/utils"
	cortypes "gitlink.org.cn/cloudream/jcs-pub/coordinator/types"
)

func init() {
	reg.RegisterBuilder[*cortypes.OBSType](newBuilder)
}

type builder struct {
	types.EmptyBuilder
	detail *clitypes.UserSpaceDetail
}

func newBuilder(detail *clitypes.UserSpaceDetail) types.StorageBuilder {
	return &builder{
		detail: detail,
	}
}

func (b *builder) FeatureDesc() types.FeatureDesc {
	return types.FeatureDesc{
		HasBypassShardWrite:  true,
		HasBypassPublicWrite: true,
		HasBypassShardRead:   true,
		HasBypassPublicRead:  true,
		HasBypassHTTPRead:    true,
	}
}

func (b *builder) CreateShardStore() (types.ShardStore, error) {
	cred, ok := b.detail.UserSpace.Credential.(*cortypes.OBSCred)
	if !ok {
		return nil, fmt.Errorf("invalid storage credential type %T for obs storage", b.detail.UserSpace.Credential)
	}

	cli, bucket, err := createClient(cred)
	if err != nil {
		return nil, err
	}

	return NewShardStore(b.detail, cred, cli, bucket)
}

func (b *builder) CreatePublicStore() (types.PublicStore, error) {
	cred, ok := b.detail.UserSpace.Credential.(*cortypes.OBSCred)
	if !ok {
		return nil, fmt.Errorf("invalid storage credential type %T for obs storage", b.detail.UserSpace.Credential)
	}

	cli, bucket, err := createClient(cred)
	if err != nil {
		return nil, err
	}

	return s3stg.NewPublicStore(b.detail, cli, bucket)
}

func createClient(cred *cortypes.OBSCred) (*s3.Client, string, error) {
	awsConfig := aws.Config{}

	cre := aws.Credentials{
		AccessKeyID:     cred.AK,
		SecretAccessKey: cred.SK,
	}
	awsConfig.Credentials = &credentials.StaticCredentialsProvider{Value: cre}
	awsConfig.Region = cred.Region

	options := []func(*s3.Options){}
	options = append(options, func(s3Opt *s3.Options) {
		s3Opt.BaseEndpoint = &cred.Endpoint
	})

	cli := s3.NewFromConfig(awsConfig, options...)
	return cli, cred.Bucket, nil
}

func (b *builder) CreateMultiparter() (types.Multiparter, error) {
	feat := utils.FindFeature[*cortypes.MultipartUploadFeature](b.detail.Storage)
	if feat == nil {
		return nil, fmt.Errorf("feature %T not found", cortypes.MultipartUploadFeature{})
	}

	cred, ok := b.detail.UserSpace.Credential.(*cortypes.OBSCred)
	if !ok {
		return nil, fmt.Errorf("invalid storage credential type %T for obs storage", b.detail.UserSpace.Credential)
	}

	cli, bucket, err := createClient(cred)
	if err != nil {
		return nil, err
	}

	return s3stg.NewMultiparter(
		b.detail,
		feat,
		bucket,
		cli,
	), nil
}

func (b *builder) CreateS2STransfer() (types.S2STransfer, error) {
	feat := utils.FindFeature[*cortypes.S2STransferFeature](b.detail.Storage)
	if feat == nil {
		return nil, fmt.Errorf("feature %T not found", cortypes.S2STransferFeature{})
	}

	cred, ok := b.detail.UserSpace.Credential.(*cortypes.OBSCred)
	if !ok {
		return nil, fmt.Errorf("invalid storage credential type %T for obs storage", b.detail.UserSpace.Credential)
	}

	return NewS2STransfer(cred, feat), nil
}
