package _123Open

import (
	"context"
	"fmt"
	"github.com/alist-org/alist/v3/internal/driver"
	"github.com/alist-org/alist/v3/internal/errs"
	"github.com/alist-org/alist/v3/internal/model"
	"github.com/alist-org/alist/v3/internal/stream"
	"github.com/alist-org/alist/v3/pkg/utils"
	"github.com/go-resty/resty/v2"
	"net/http"
	"strconv"
)

type Open123 struct {
	model.Storage
	Addition

	UploadThread int
	tm           *tokenManager
}

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

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

func (d *Open123) Init(ctx context.Context) error {
	d.tm = newTokenManager(d.ClientID, d.ClientSecret)

	if _, err := d.tm.getToken(); err != nil {
		return fmt.Errorf("token 初始化失败: %w", err)
	}

	return nil
}

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

func (d *Open123) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
	parentFileId, err := strconv.ParseInt(dir.GetID(), 10, 64)
	if err != nil {
		return nil, err
	}

	fileLastId := int64(0)
	var results []File

	for fileLastId != -1 {
		files, err := d.getFiles(parentFileId, 100, fileLastId)
		if err != nil {
			return nil, err
		}
		for _, f := range files.Data.FileList {
			if f.Trashed == 0 {
				results = append(results, f)
			}
		}
		fileLastId = files.Data.LastFileId
	}

	objs := make([]model.Obj, 0, len(results))
	for _, f := range results {
		objs = append(objs, f)
	}
	return objs, nil
}

func (d *Open123) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
	if file.IsDir() {
		return nil, errs.LinkIsDir
	}

	fileID := file.GetID()

	var result DirectLinkResp
	url := fmt.Sprintf("%s?fileID=%s", ApiGetDirectLink, fileID)
	_, err := d.Request(url, http.MethodGet, nil, &result)
	if err != nil {
		return nil, err
	}
	if result.Code != 0 {
		return nil, fmt.Errorf("get link failed: %s", result.Message)
	}

	return &model.Link{
		URL: result.Data.URL,
	}, nil
}

func (d *Open123) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
	parentID, err := strconv.ParseInt(parentDir.GetID(), 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid parent ID: %w", err)
	}

	var result MakeDirResp
	reqBody := MakeDirRequest{
		Name:     dirName,
		ParentID: parentID,
	}

	_, err = d.Request(ApiMakeDir, http.MethodPost, func(r *resty.Request) {
		r.SetBody(reqBody)
	}, &result)
	if err != nil {
		return nil, err
	}
	if result.Code != 0 {
		return nil, fmt.Errorf("mkdir failed: %s", result.Message)
	}

	newDir := File{
		FileId:       result.Data.DirID,
		FileName:     dirName,
		Type:         1,
		ParentFileId: int(parentID),
		Size:         0,
		Trashed:      0,
	}
	return newDir, nil
}

func (d *Open123) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
	srcID, err := strconv.ParseInt(srcObj.GetID(), 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid src file ID: %w", err)
	}
	dstID, err := strconv.ParseInt(dstDir.GetID(), 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid dest dir ID: %w", err)
	}

	var result Response[any]
	reqBody := map[string]interface{}{
		"fileIDs":        []int64{srcID},
		"toParentFileID": dstID,
	}

	_, err = d.Request(ApiMove, http.MethodPost, func(r *resty.Request) {
		r.SetBody(reqBody)
	}, &result)
	if err != nil {
		return nil, err
	}
	if result.Code != 0 {
		return nil, fmt.Errorf("move failed: %s", result.Message)
	}

	files, err := d.getFiles(dstID, 100, 0)
	if err != nil {
		return nil, fmt.Errorf("move succeed but failed to get target dir: %w", err)
	}
	for _, f := range files.Data.FileList {
		if f.FileId == srcID {
			return f, nil
		}
	}
	return nil, fmt.Errorf("move succeed but file not found in target dir")
}

func (d *Open123) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
	srcID, err := strconv.ParseInt(srcObj.GetID(), 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid file ID: %w", err)
	}

	var result Response[any]
	reqBody := map[string]interface{}{
		"fileId":   srcID,
		"fileName": newName,
	}

	_, err = d.Request(ApiRename, http.MethodPut, func(r *resty.Request) {
		r.SetBody(reqBody)
	}, &result)
	if err != nil {
		return nil, err
	}
	if result.Code != 0 {
		return nil, fmt.Errorf("rename failed: %s", result.Message)
	}

	parentID := 0
	if file, ok := srcObj.(File); ok {
		parentID = file.ParentFileId
	}
	files, err := d.getFiles(int64(parentID), 100, 0)
	if err != nil {
		return nil, fmt.Errorf("rename succeed but failed to get parent dir: %w", err)
	}
	for _, f := range files.Data.FileList {
		if f.FileId == srcID {
			return f, nil
		}
	}
	return nil, fmt.Errorf("rename succeed but file not found in parent dir")
}

func (d *Open123) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
	return nil, errs.NotSupport
}

func (d *Open123) Remove(ctx context.Context, obj model.Obj) error {
	idStr := obj.GetID()
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		return fmt.Errorf("invalid file ID: %w", err)
	}

	var result Response[any]
	reqBody := RemoveRequest{
		FileIDs: []int64{id},
	}

	_, err = d.Request(ApiRemove, http.MethodPost, func(r *resty.Request) {
		r.SetBody(reqBody)
	}, &result)
	if err != nil {
		return err
	}
	if result.Code != 0 {
		return fmt.Errorf("remove failed: %s", result.Message)
	}

	return nil
}

func (d *Open123) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) error {
	parentFileId, err := strconv.ParseInt(dstDir.GetID(), 10, 64)
	etag := file.GetHash().GetHash(utils.MD5)

	if len(etag) < utils.MD5.Width {
		up = model.UpdateProgressWithRange(up, 50, 100)
		_, etag, err = stream.CacheFullInTempFileAndHash(file, utils.MD5)
		if err != nil {
			return err
		}
	}
	createResp, err := d.create(parentFileId, file.GetName(), etag, file.GetSize(), 2, false)
	if err != nil {
		return err
	}
	if createResp.Data.Reuse {
		return nil
	}

	return d.Upload(ctx, file, parentFileId, createResp, up)
}

func (d *Open123) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
	return nil, errs.NotSupport
}

func (d *Open123) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
	return nil, errs.NotSupport
}

func (d *Open123) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
	return nil, errs.NotSupport
}

func (d *Open123) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error) {
	return nil, errs.NotSupport
}

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

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