package ali

import (
	"context"
	"path/filepath"
	"strings"
	"time"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
)

type OSSOption func(*OSSOptions)

type OSSOptions struct {
	internal bool
	expire   *time.Duration
}

func WithExpire(expire time.Duration) OSSOption {
	return func(o *OSSOptions) {
		o.expire = &expire
	}
}

func WithInternal() OSSOption {
	return func(o *OSSOptions) {
		o.internal = true
	}
}

type OSSClient struct {
	client         *oss.Client
	internalClient *oss.Client
	config         *Config
}

func (c *OSSClient) GetSavePath(path string) string {
	return filepath.Join("quyun", strings.Trim(path, "/"))
}

func (c *OSSClient) GetClient() *oss.Client {
	return c.client
}

func (c *OSSClient) PreSignUpload(ctx context.Context, path, mimeType string, opts ...OSSOption) (*oss.PresignResult, error) {
	request := &oss.PutObjectRequest{
		Bucket:      oss.Ptr(c.config.Bucket),
		Key:         oss.Ptr(c.GetSavePath(path)),
		ContentType: oss.Ptr(mimeType),
	}
	opt := &OSSOptions{}
	for _, o := range opts {
		o(opt)
	}

	client := c.client
	if opt.internal {
		client = c.internalClient
	}

	return client.Presign(ctx, request)
}

func (c *OSSClient) Download(ctx context.Context, path, dest string, opts ...OSSOption) error {
	request := &oss.GetObjectRequest{
		Bucket: oss.Ptr(c.config.Bucket),
		Key:    oss.Ptr(path),
	}

	opt := &OSSOptions{}
	for _, o := range opts {
		o(opt)
	}

	client := c.client
	if opt.internal {
		client = c.internalClient
	}

	ossFuncs := []func(*oss.Options){
		oss.OpReadWriteTimeout(time.Minute * 20),
	}
	if _, err := client.GetObjectToFile(ctx, request, dest, ossFuncs...); err != nil {
		return err
	}
	return nil
}

// GetSignedUrl
func (c *OSSClient) GetSignedUrl(ctx context.Context, path string, opts ...OSSOption) (string, error) {
	request := &oss.GetObjectRequest{
		Bucket: oss.Ptr(c.config.Bucket),
		Key:    oss.Ptr(path),
	}

	opt := &OSSOptions{}
	for _, o := range opts {
		o(opt)
	}

	expire := time.Minute * 5
	if opt.expire != nil {
		expire = *opt.expire
	}

	client := c.client
	if opt.internal {
		client = c.internalClient
	}

	preSign, err := client.Presign(ctx, request, oss.PresignExpires(expire))
	if err != nil {
		return "", err
	}
	return preSign.URL, nil
}

// Delete
func (c *OSSClient) Delete(ctx context.Context, path string, opts ...OSSOption) error {
	request := &oss.DeleteObjectRequest{
		Bucket: oss.Ptr(c.config.Bucket),
		Key:    oss.Ptr(path),
	}

	opt := &OSSOptions{}
	for _, o := range opts {
		o(opt)
	}

	client := c.client
	if opt.internal {
		client = c.internalClient
	}

	if _, err := client.DeleteObject(ctx, request); err != nil {
		return err
	}
	return nil
}

// Upload
func (c *OSSClient) Upload(ctx context.Context, input, dst string, opts ...OSSOption) error {
	request := &oss.PutObjectRequest{
		Bucket: oss.Ptr(c.config.Bucket),
		Key:    oss.Ptr(dst),
	}

	opt := &OSSOptions{}
	for _, o := range opts {
		o(opt)
	}

	client := c.client
	if opt.internal {
		client = c.internalClient
	}

	ossFuncs := []func(*oss.Options){
		oss.OpReadWriteTimeout(time.Minute * 20),
	}
	if _, err := client.PutObjectFromFile(ctx, request, input, ossFuncs...); err != nil {
		return err
	}
	return nil
}
