package cloudreve_v4

import (
	"context"
	"errors"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/AlliotTech/openalist/drivers/base"
	"github.com/AlliotTech/openalist/internal/driver"
	"github.com/AlliotTech/openalist/internal/errs"
	"github.com/AlliotTech/openalist/internal/model"
	"github.com/AlliotTech/openalist/internal/op"
	"github.com/AlliotTech/openalist/pkg/utils"
	"github.com/go-resty/resty/v2"
)

type CloudreveV4 struct {
	model.Storage
	Addition
	ref *CloudreveV4
}

func (d *CloudreveV4) Config() driver.Config {
	if d.ref != nil {
		return d.ref.Config()
	}
	if d.EnableVersionUpload {
		config.NoOverwriteUpload = false
	}
	return config
}

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

func (d *CloudreveV4) Init(ctx context.Context) error {
	// removing trailing slash
	d.Address = strings.TrimSuffix(d.Address, "/")
	op.MustSaveDriverStorage(d)
	if d.ref != nil {
		return nil
	}
	if d.AccessToken == "" && d.RefreshToken != "" {
		return d.refreshToken()
	}
	if d.Username != "" {
		return d.login()
	}
	return nil
}

func (d *CloudreveV4) InitReference(storage driver.Driver) error {
	refStorage, ok := storage.(*CloudreveV4)
	if ok {
		d.ref = refStorage
		return nil
	}
	return errs.NotSupport
}

func (d *CloudreveV4) Drop(ctx context.Context) error {
	d.ref = nil
	return nil
}

func (d *CloudreveV4) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
	const pageSize int = 100
	var f []File
	var r FileResp
	params := map[string]string{
		"page_size":       strconv.Itoa(pageSize),
		"uri":             dir.GetPath(),
		"order_by":        d.OrderBy,
		"order_direction": d.OrderDirection,
		"page":            "0",
	}

	for {
		err := d.request(http.MethodGet, "/file", func(req *resty.Request) {
			req.SetQueryParams(params)
		}, &r)
		if err != nil {
			return nil, err
		}
		f = append(f, r.Files...)
		if r.Pagination.NextToken == "" || len(r.Files) < pageSize {
			break
		}
		params["next_page_token"] = r.Pagination.NextToken
	}

	return utils.SliceConvert(f, func(src File) (model.Obj, error) {
		if d.EnableFolderSize && src.Type == 1 {
			var ds FolderSummaryResp
			err := d.request(http.MethodGet, "/file/info", func(req *resty.Request) {
				req.SetQueryParam("uri", src.Path)
				req.SetQueryParam("folder_summary", "true")
			}, &ds)
			if err == nil && ds.FolderSummary.Size > 0 {
				src.Size = ds.FolderSummary.Size
			}
		}
		var thumb model.Thumbnail
		if d.EnableThumb && src.Type == 0 {
			var t FileThumbResp
			err := d.request(http.MethodGet, "/file/thumb", func(req *resty.Request) {
				req.SetQueryParam("uri", src.Path)
			}, &t)
			if err == nil && t.URL != "" {
				thumb = model.Thumbnail{
					Thumbnail: t.URL,
				}
			}
		}
		return &model.ObjThumb{
			Object: model.Object{
				ID:       src.ID,
				Path:     src.Path,
				Name:     src.Name,
				Size:     src.Size,
				Modified: src.UpdatedAt,
				Ctime:    src.CreatedAt,
				IsFolder: src.Type == 1,
			},
			Thumbnail: thumb,
		}, nil
	})
}

func (d *CloudreveV4) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
	var url FileUrlResp
	err := d.request(http.MethodPost, "/file/url", func(req *resty.Request) {
		req.SetBody(base.Json{
			"uris":     []string{file.GetPath()},
			"download": true,
		})
	}, &url)
	if err != nil {
		return nil, err
	}
	if len(url.Urls) == 0 {
		return nil, errors.New("server returns no url")
	}
	exp := time.Until(url.Expires)
	return &model.Link{
		URL:        url.Urls[0].URL,
		Expiration: &exp,
	}, nil
}

func (d *CloudreveV4) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
	return d.request(http.MethodPost, "/file/create", func(req *resty.Request) {
		req.SetBody(base.Json{
			"type":              "folder",
			"uri":               parentDir.GetPath() + "/" + dirName,
			"error_on_conflict": true,
		})
	}, nil)
}

func (d *CloudreveV4) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
	return d.request(http.MethodPost, "/file/move", func(req *resty.Request) {
		req.SetBody(base.Json{
			"uris": []string{srcObj.GetPath()},
			"dst":  dstDir.GetPath(),
			"copy": false,
		})
	}, nil)
}

func (d *CloudreveV4) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
	return d.request(http.MethodPost, "/file/create", func(req *resty.Request) {
		req.SetBody(base.Json{
			"new_name": newName,
			"uri":      srcObj.GetPath(),
		})
	}, nil)

}

func (d *CloudreveV4) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
	return d.request(http.MethodPost, "/file/move", func(req *resty.Request) {
		req.SetBody(base.Json{
			"uris": []string{srcObj.GetPath()},
			"dst":  dstDir.GetPath(),
			"copy": true,
		})
	}, nil)
}

func (d *CloudreveV4) Remove(ctx context.Context, obj model.Obj) error {
	return d.request(http.MethodDelete, "/file", func(req *resty.Request) {
		req.SetBody(base.Json{
			"uris":             []string{obj.GetPath()},
			"unlink":           false,
			"skip_soft_delete": true,
		})
	}, nil)
}

func (d *CloudreveV4) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) error {
	if file.GetSize() == 0 {
		// 空文件使用新建文件方法，避免上传卡锁
		return d.request(http.MethodPost, "/file/create", func(req *resty.Request) {
			req.SetBody(base.Json{
				"type":              "file",
				"uri":               dstDir.GetPath() + "/" + file.GetName(),
				"error_on_conflict": true,
			})
		}, nil)
	}
	var p StoragePolicy
	var r FileResp
	var u FileUploadResp
	var err error
	params := map[string]string{
		"page_size":       "10",
		"uri":             dstDir.GetPath(),
		"order_by":        "created_at",
		"order_direction": "asc",
		"page":            "0",
	}
	err = d.request(http.MethodGet, "/file", func(req *resty.Request) {
		req.SetQueryParams(params)
	}, &r)
	if err != nil {
		return err
	}
	p = r.StoragePolicy
	body := base.Json{
		"uri":           dstDir.GetPath() + "/" + file.GetName(),
		"size":          file.GetSize(),
		"policy_id":     p.ID,
		"last_modified": file.ModTime().UnixMilli(),
		"mime_type":     "",
	}
	if d.EnableVersionUpload {
		body["entity_type"] = "version"
	}
	err = d.request(http.MethodPut, "/file/upload", func(req *resty.Request) {
		req.SetBody(body)
	}, &u)
	if err != nil {
		return err
	}
	if u.StoragePolicy.Relay {
		err = d.upLocal(ctx, file, u, up)
	} else {
		switch u.StoragePolicy.Type {
		case "local":
			err = d.upLocal(ctx, file, u, up)
		case "remote":
			err = d.upRemote(ctx, file, u, up)
		case "onedrive":
			err = d.upOneDrive(ctx, file, u, up)
		case "s3":
			err = d.upS3(ctx, file, u, up)
		default:
			return errs.NotImplement
		}
	}
	if err != nil {
		// 删除失败的会话
		_ = d.request(http.MethodDelete, "/file/upload", func(req *resty.Request) {
			req.SetBody(base.Json{
				"id":  u.SessionID,
				"uri": u.URI,
			})
		}, nil)
		return err
	}
	return nil
}

func (d *CloudreveV4) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
	// TODO get archive file meta-info, return errs.NotImplement to use an internal archive tool, optional
	return nil, errs.NotImplement
}

func (d *CloudreveV4) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
	// TODO list args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional
	return nil, errs.NotImplement
}

func (d *CloudreveV4) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
	// TODO return link of file args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional
	return nil, errs.NotImplement
}

func (d *CloudreveV4) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error) {
	// TODO extract args.InnerPath path in the archive srcObj to the dstDir location, optional
	// a folder with the same name as the archive file needs to be created to store the extracted results if args.PutIntoNewDir
	// return errs.NotImplement to use an internal archive tool
	return nil, errs.NotImplement
}

//func (d *CloudreveV4) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
//	return nil, errs.NotSupport
//}

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