package uss

import (
	"context"
	"fmt"
	"net/url"
	"path"
	"strings"
	"time"

	"github.com/alist-org/alist/v3/internal/driver"
	"github.com/alist-org/alist/v3/internal/model"
	"github.com/alist-org/alist/v3/pkg/utils"
	"github.com/upyun/go-sdk/v3/upyun"
)

type USS struct {
	model.Storage
	Addition
	client *upyun.UpYun
}

func (d *USS) Config() driver.Config {
	return config
}

func (d *USS) GetAddition() driver.Additional {
	return &d.Addition
}

func (d *USS) Init(ctx context.Context) error {
	d.client = upyun.NewUpYun(&upyun.UpYunConfig{
		Bucket:   d.Bucket,
		Operator: d.OperatorName,
		Password: d.OperatorPassword,
	})
	return nil
}

func (d *USS) Drop(ctx context.Context) error {
	return nil
}

func (d *USS) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
	prefix := getKey(dir.GetPath(), true)
	objsChan := make(chan *upyun.FileInfo, 10)
	var err error
	go func() {
		err = d.client.List(&upyun.GetObjectsConfig{
			Path:           prefix,
			ObjectsChan:    objsChan,
			MaxListObjects: 0,
			MaxListLevel:   1,
		})
	}()
	if err != nil {
		return nil, err
	}
	res := make([]model.Obj, 0)
	for obj := range objsChan {
		t := obj.Time
		f := model.Object{
			Name:     obj.Name,
			Size:     obj.Size,
			Modified: t,
			IsFolder: obj.IsDir,
		}
		res = append(res, &f)
	}
	return res, err
}

func (d *USS) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
	key := getKey(file.GetPath(), false)
	host := d.Endpoint
	if !strings.Contains(host, "://") { //判断是否包含协议头，否则https
		host = "https://" + host
	}
	u := fmt.Sprintf("%s/%s", host, key)
	downExp := time.Hour * time.Duration(d.SignURLExpire)
	expireAt := time.Now().Add(downExp).Unix()
	upd := url.QueryEscape(path.Base(file.GetPath()))
	signStr := strings.Join([]string{d.OperatorPassword, fmt.Sprint(expireAt), fmt.Sprintf("/%s", key)}, "&")
	upt := utils.GetMD5Encode(signStr)[12:20] + fmt.Sprint(expireAt)
	link := fmt.Sprintf("%s?_upd=%s&_upt=%s", u, upd, upt)
	return &model.Link{URL: link}, nil
}

func (d *USS) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
	return d.client.Mkdir(getKey(path.Join(parentDir.GetPath(), dirName), true))
}

func (d *USS) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
	return d.client.Move(&upyun.MoveObjectConfig{
		SrcPath:  getKey(srcObj.GetPath(), srcObj.IsDir()),
		DestPath: getKey(path.Join(dstDir.GetPath(), srcObj.GetName()), srcObj.IsDir()),
	})
}

func (d *USS) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
	return d.client.Move(&upyun.MoveObjectConfig{
		SrcPath:  getKey(srcObj.GetPath(), srcObj.IsDir()),
		DestPath: getKey(path.Join(path.Dir(srcObj.GetPath()), newName), srcObj.IsDir()),
	})
}

func (d *USS) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
	return d.client.Copy(&upyun.CopyObjectConfig{
		SrcPath:  getKey(srcObj.GetPath(), srcObj.IsDir()),
		DestPath: getKey(path.Join(dstDir.GetPath(), srcObj.GetName()), srcObj.IsDir()),
	})
}

func (d *USS) Remove(ctx context.Context, obj model.Obj) error {
	return d.client.Delete(&upyun.DeleteObjectConfig{
		Path:  getKey(obj.GetPath(), obj.IsDir()),
		Async: false,
	})
}

func (d *USS) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
	// TODO not support cancel??
	return d.client.Put(&upyun.PutObjectConfig{
		Path:   getKey(path.Join(dstDir.GetPath(), stream.GetName()), false),
		Reader: stream,
	})
}

var _ driver.Driver = (*USS)(nil)
