package api

import (
	"context"
	"fmt"
	"io"
	"mime"
	"net/http"
	"net/url"
	"strings"
	"time"

	v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"gitlink.org.cn/cloudream/common/consts/errorcode"
	"gitlink.org.cn/cloudream/common/pkgs/iterator"
	"gitlink.org.cn/cloudream/common/sdks"
	"gitlink.org.cn/cloudream/common/utils/http2"
	"gitlink.org.cn/cloudream/common/utils/serder"
	"gitlink.org.cn/cloudream/jcs-pub/client/types"
)

type ObjectService struct {
	*Client
}

func (c *Client) Object() *ObjectService {
	return &ObjectService{
		Client: c,
	}
}

const ObjectListPathByPath = "/object/listByPath"

type ObjectListByPath struct {
	PackageID         types.PackageID `form:"packageID" binding:"required" url:"packageID" json:"packageID"`
	Path              string          `form:"path" url:"path" json:"path"` // 允许为空字符串
	IsPrefix          bool            `form:"isPrefix" url:"isPrefix" json:"isPrefix"`
	NoRecursive       bool            `form:"noRecursive" url:"noRecursive" json:"noRecursive"` // 仅当isPrefix为true时有效，表示仅查询直接属于Prefix下的对象，对于更深的对象，返回它们的公共前缀
	MaxKeys           int             `form:"maxKeys" url:"maxKeys" json:"maxKeys"`
	ContinuationToken string          `form:"continuationToken" url:"continuationToken" json:"continuationToken"` // 用于分页，如果为空字符串，表示从头开始
}

func (r *ObjectListByPath) MakeParam() *sdks.RequestParam {
	return sdks.MakeQueryParam(http.MethodGet, ObjectListPathByPath, r)
}

type ObjectListByPathResp struct {
	CommonPrefixes        []string       `json:"commonPrefixes"`        // 仅在IsPrefix为true且NoRecursive为true时有效，包含更深层对象的shared prefix
	Objects               []types.Object `json:"objects"`               // 如果IsPrefix为true且NoRecursive为false，则返回所有匹配的对象，否则只返回直接属于Prefix下的对象
	IsTruncated           bool           `json:"isTruncated"`           // 是否还有更多对象
	NextContinuationToken string         `json:"nextContinuationToken"` // 用于分页，如果IsTruncated为true，则下次请求的ContinuationToken为该值
}

func (r *ObjectListByPathResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) ListByPath(req ObjectListByPath) (*ObjectListByPathResp, error) {
	return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectListByPathResp{})
}

const ObjectListByIDsPath = "/object/listByIDs"

type ObjectListByIDs struct {
	ObjectIDs []types.ObjectID `form:"objectIDs" binding:"required" url:"objectIDs"`
}

func (r *ObjectListByIDs) MakeParam() *sdks.RequestParam {
	return sdks.MakeQueryParam(http.MethodGet, ObjectListByIDsPath, r)
}

type ObjectListByIDsResp struct {
	Objects []*types.Object `json:"object"` // 与ObjectIDs一一对应，如果某个ID不存在，则对应位置为nil
}

func (r *ObjectListByIDsResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) ListByIDs(req ObjectListByIDs) (*ObjectListByIDsResp, error) {
	return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectListByIDsResp{})
}

const ObjectUploadPath = "/object/upload"

type ObjectUpload struct {
	ObjectUploadInfo
	Files UploadObjectIterator `json:"-"`
}

type ObjectUploadInfo struct {
	PackageID  types.PackageID     `json:"packageID" binding:"required"`
	Affinity   types.UserSpaceID   `json:"affinity"`
	LoadTo     []types.UserSpaceID `json:"loadTo"`
	LoadToPath []string            `json:"loadToPath"`
}

type UploadingObject struct {
	Path string
	File io.ReadCloser
}

type UploadObjectIterator = iterator.Iterator[*UploadingObject]

type ObjectUploadResp struct {
	Uploadeds []types.Object `json:"uploadeds"`
}

func (c *ObjectService) Upload(req ObjectUpload) (*ObjectUploadResp, error) {
	type uploadInfo struct {
		Info string `url:"info"`
	}

	url, err := url.JoinPath(c.cfg.URL, ObjectUploadPath)
	if err != nil {
		return nil, err
	}

	infoJSON, err := serder.ObjectToJSON(req)
	if err != nil {
		return nil, fmt.Errorf("upload info to json: %w", err)
	}

	resp, err := PostMultiPart(c.cfg, url,
		uploadInfo{Info: string(infoJSON)},
		iterator.Map(req.Files, func(src *UploadingObject) (*http2.IterMultiPartFile, error) {
			return &http2.IterMultiPartFile{
				FieldName: "files",
				FileName:  src.Path,
				File:      src.File,
			}, nil
		}))
	if err != nil {
		return nil, err
	}

	contType := resp.Header.Get("Content-Type")
	if strings.Contains(contType, http2.ContentTypeJSON) {
		var err error
		var codeResp response[ObjectUploadResp]
		if codeResp, err = serder.JSONToObjectStreamEx[response[ObjectUploadResp]](resp.Body); err != nil {
			return nil, fmt.Errorf("parsing response: %w", err)
		}

		if codeResp.Code == errorcode.OK {
			return &codeResp.Data, nil
		}

		return nil, codeResp.ToError()
	}

	return nil, fmt.Errorf("unknow response content type: %s", contType)

}

const ObjectDownloadPath = "/object/download"

type ObjectDownload struct {
	ObjectID types.ObjectID `form:"objectID" url:"objectID" binding:"required"`
	Offset   int64          `form:"offset" url:"offset,omitempty"`
	Length   *int64         `form:"length" url:"length,omitempty"`
}

func (r *ObjectDownload) MakeParam() *sdks.RequestParam {
	return sdks.MakeQueryParam(http.MethodGet, ObjectDownloadPath, r)
}

type DownloadingObject struct {
	Path string
	File io.ReadCloser
}

func (c *ObjectService) Download(req ObjectDownload) (*DownloadingObject, error) {
	httpReq, err := req.MakeParam().MakeRequest(c.cfg.URL)
	if err != nil {
		return nil, err
	}

	if c.cfg.AccessKey != "" && c.cfg.SecretKey != "" {
		prod := credentials.NewStaticCredentialsProvider(c.cfg.AccessKey, c.cfg.SecretKey, "")
		cred, err := prod.Retrieve(context.TODO())
		if err != nil {
			return nil, err
		}

		signer := v4.NewSigner()
		err = signer.SignHTTP(context.Background(), cred, httpReq, "", AuthService, AuthRegion, time.Now())
		if err != nil {
			return nil, err
		}
	}

	resp, err := http.DefaultClient.Do(httpReq)
	if err != nil {
		return nil, err
	}

	contType := resp.Header.Get("Content-Type")

	if strings.Contains(contType, http2.ContentTypeJSON) {
		var codeResp response[any]
		if err := serder.JSONToObjectStream(resp.Body, &codeResp); err != nil {
			return nil, fmt.Errorf("parsing response: %w", err)
		}

		return nil, codeResp.ToError()
	}

	_, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
	if err != nil {
		return nil, fmt.Errorf("parsing content disposition: %w", err)
	}

	return &DownloadingObject{
		Path: params["filename"],
		File: resp.Body,
	}, nil
}

const ObjectDownloadByPathPath = "/object/downloadByPath"

type ObjectDownloadByPath struct {
	PackageID types.PackageID `form:"packageID" url:"packageID" binding:"required"`
	Path      string          `form:"path" url:"path" binding:"required"`
	Offset    int64           `form:"offset" url:"offset,omitempty"`
	Length    *int64          `form:"length" url:"length,omitempty"`
}

func (r *ObjectDownloadByPath) MakeParam() *sdks.RequestParam {
	return sdks.MakeQueryParam(http.MethodGet, ObjectDownloadByPathPath, r)
}

func (c *ObjectService) DownloadByPath(req ObjectDownloadByPath) (*DownloadingObject, error) {
	httpReq, err := req.MakeParam().MakeRequest(c.cfg.URL)
	if err != nil {
		return nil, err
	}

	if c.cfg.AccessKey != "" && c.cfg.SecretKey != "" {
		prod := credentials.NewStaticCredentialsProvider(c.cfg.AccessKey, c.cfg.SecretKey, "")
		cred, err := prod.Retrieve(context.TODO())
		if err != nil {
			return nil, err
		}

		signer := v4.NewSigner()
		err = signer.SignHTTP(context.Background(), cred, httpReq, "", AuthService, AuthRegion, time.Now())
		if err != nil {
			return nil, err
		}
	}

	resp, err := http.DefaultClient.Do(httpReq)
	if err != nil {
		return nil, err
	}

	contType := resp.Header.Get("Content-Type")

	if strings.Contains(contType, http2.ContentTypeJSON) {
		var codeResp response[any]
		if err := serder.JSONToObjectStream(resp.Body, &codeResp); err != nil {
			return nil, fmt.Errorf("parsing response: %w", err)
		}

		return nil, codeResp.ToError()
	}

	_, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition"))
	if err != nil {
		return nil, fmt.Errorf("parsing content disposition: %w", err)
	}

	return &DownloadingObject{
		Path: params["filename"],
		File: resp.Body,
	}, nil
}

const ObjectUpdateInfoPath = "/object/updateInfo"

type UpdatingObject struct {
	ObjectID   types.ObjectID `json:"objectID" binding:"required"`
	UpdateTime time.Time      `json:"updateTime" binding:"required"`
}

func (u *UpdatingObject) ApplyTo(obj *types.Object) {
	obj.UpdateTime = u.UpdateTime
}

type ObjectUpdateInfo struct {
	Updatings []UpdatingObject `json:"updatings" binding:"required"`
}

func (r *ObjectUpdateInfo) MakeParam() *sdks.RequestParam {
	return sdks.MakeJSONParam(http.MethodPost, ObjectUpdateInfoPath, r)
}

type ObjectUpdateInfoResp struct {
	Successes []types.ObjectID `json:"successes"`
}

func (r *ObjectUpdateInfoResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) UpdateInfo(req ObjectUpdateInfo) (*ObjectUpdateInfoResp, error) {
	return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectUpdateInfoResp{})
}

const ObjectUpdateInfoByPathPath = "/object/updateInfoByPath"

type ObjectUpdateInfoByPath struct {
	PackageID  types.PackageID `json:"packageID" binding:"required"`
	Path       string          `json:"path" binding:"required"`
	UpdateTime time.Time       `json:"updateTime" binding:"required"`
}

func (r *ObjectUpdateInfoByPath) MakeParam() *sdks.RequestParam {
	return sdks.MakeJSONParam(http.MethodPost, ObjectUpdateInfoByPathPath, r)
}

type ObjectUpdateInfoByPathResp struct{}

func (r *ObjectUpdateInfoByPathResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) UpdateInfoByPath(req ObjectUpdateInfoByPath) (*ObjectUpdateInfoByPathResp, error) {
	return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectUpdateInfoByPathResp{})
}

const ObjectMovePath = "/object/move"

type MovingObject struct {
	ObjectID  types.ObjectID  `json:"objectID" binding:"required"`
	PackageID types.PackageID `json:"packageID" binding:"required"`
	Path      string          `json:"path" binding:"required"`
}

func (m *MovingObject) ApplyTo(obj *types.Object) {
	obj.PackageID = m.PackageID
	obj.Path = m.Path
}

type ObjectMove struct {
	Movings []MovingObject `json:"movings" binding:"required"`
}

func (r *ObjectMove) MakeParam() *sdks.RequestParam {
	return sdks.MakeJSONParam(http.MethodPost, ObjectMovePath, r)
}

type ObjectMoveResp struct {
	Successes []types.ObjectID `json:"successes"`
}

func (r *ObjectMoveResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) Move(req ObjectMove) (*ObjectMoveResp, error) {
	return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectMoveResp{})
}

const ObjectDeletePath = "/object/delete"

type ObjectDelete struct {
	ObjectIDs []types.ObjectID `json:"objectIDs" binding:"required"`
}

func (r *ObjectDelete) MakeParam() *sdks.RequestParam {
	return sdks.MakeJSONParam(http.MethodPost, ObjectDeletePath, r)
}

type ObjectDeleteResp struct{}

func (r *ObjectDeleteResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) Delete(req ObjectDelete) error {
	return JSONAPINoData(c.cfg, http.DefaultClient, &req)
}

const ObjectDeleteByPathPath = "/object/deleteByPath"

type ObjectDeleteByPath struct {
	PackageID types.PackageID `json:"packageID" binding:"required"`
	Path      string          `json:"path" binding:"required"`
}

func (r *ObjectDeleteByPath) MakeParam() *sdks.RequestParam {
	return sdks.MakeJSONParam(http.MethodPost, ObjectDeleteByPathPath, r)
}

type ObjectDeleteByPathResp struct{}

func (r *ObjectDeleteByPathResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) DeleteByPath(req ObjectDeleteByPath) error {
	return JSONAPINoData(c.cfg, http.DefaultClient, &req)
}

const ObjectClonePath = "/object/clone"

type ObjectClone struct {
	Clonings []CloningObject `json:"clonings" binding:"required"`
}

func (r *ObjectClone) MakeParam() *sdks.RequestParam {
	return sdks.MakeJSONParam(http.MethodPost, ObjectClonePath, r)
}

type CloningObject struct {
	ObjectID     types.ObjectID  `json:"objectID" binding:"required"`
	NewPath      string          `json:"newPath" binding:"required"`
	NewPackageID types.PackageID `json:"newPackageID" binding:"required"`
}

type ObjectCloneResp struct {
	Objects []*types.Object `json:"objects"`
}

func (r *ObjectCloneResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) Clone(req ObjectClone) (*ObjectCloneResp, error) {
	return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectCloneResp{})
}

const ObjectGetPackageObjectsPath = "/object/getPackageObjects"

type ObjectGetPackageObjects struct {
	PackageID types.PackageID `form:"packageID" url:"packageID" binding:"required"`
}

func (r *ObjectGetPackageObjects) MakeParam() *sdks.RequestParam {
	return sdks.MakeQueryParam(http.MethodGet, ObjectGetPackageObjectsPath, r)
}

type ObjectGetPackageObjectsResp struct {
	Objects []types.Object `json:"objects"`
}

func (r *ObjectGetPackageObjectsResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) GetPackageObjects(req ObjectGetPackageObjects) (*ObjectGetPackageObjectsResp, error) {
	return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectGetPackageObjectsResp{})
}

const ObjectNewMultipartUploadPath = "/v1/object/newMultipartUpload"

type ObjectNewMultipartUpload struct {
	PackageID types.PackageID `json:"packageID" binding:"required"`
	Path      string          `json:"path" binding:"required"`
}

func (r *ObjectNewMultipartUpload) MakeParam() *sdks.RequestParam {
	return sdks.MakeJSONParam(http.MethodPost, ObjectNewMultipartUploadPath, r)
}

type ObjectNewMultipartUploadResp struct {
	Object types.Object `json:"object"`
}

func (r *ObjectNewMultipartUploadResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) NewMultipartUpload(req ObjectNewMultipartUpload) (*ObjectNewMultipartUploadResp, error) {
	return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectNewMultipartUploadResp{})
}

const ObjectUploadPartPath = "/v1/object/uploadPart"

type ObjectUploadPart struct {
	ObjectUploadPartInfo
	File io.ReadCloser `json:"-"`
}

type ObjectUploadPartInfo struct {
	ObjectID types.ObjectID `json:"objectID" binding:"required"`
	Index    int            `json:"index"`
}

type ObjectUploadPartResp struct{}

func (c *ObjectService) UploadPart(req ObjectUploadPart) (*ObjectUploadPartResp, error) {
	url, err := url.JoinPath(c.cfg.URL, ObjectUploadPartPath)
	if err != nil {
		return nil, err
	}

	infoJSON, err := serder.ObjectToJSON(req)
	if err != nil {
		return nil, fmt.Errorf("upload info to json: %w", err)
	}

	resp, err := http2.PostMultiPart(url, http2.MultiPartRequestParam{
		Form: map[string]string{"info": string(infoJSON)},
		Files: iterator.Array(&http2.IterMultiPartFile{
			FieldName: "file",
			File:      req.File,
		}),
	})
	if err != nil {
		return nil, err
	}

	contType := resp.Header.Get("Content-Type")
	if strings.Contains(contType, http2.ContentTypeJSON) {
		var err error
		var codeResp response[ObjectUploadPartResp]
		if codeResp, err = serder.JSONToObjectStreamEx[response[ObjectUploadPartResp]](resp.Body); err != nil {
			return nil, fmt.Errorf("parsing response: %w", err)
		}

		if codeResp.Code == errorcode.OK {
			return &codeResp.Data, nil
		}

		return nil, codeResp.ToError()
	}

	return nil, fmt.Errorf("unknow response content type: %s", contType)
}

const ObjectCompleteMultipartUploadPath = "/v1/object/completeMultipartUpload"

type ObjectCompleteMultipartUpload struct {
	ObjectID types.ObjectID `json:"objectID" binding:"required"`
	Indexes  []int          `json:"indexes" binding:"required"`
}

func (r *ObjectCompleteMultipartUpload) MakeParam() *sdks.RequestParam {
	return sdks.MakeJSONParam(http.MethodPost, ObjectCompleteMultipartUploadPath, r)
}

type ObjectCompleteMultipartUploadResp struct {
	Object types.Object `json:"object"`
}

func (r *ObjectCompleteMultipartUploadResp) ParseResponse(resp *http.Response) error {
	return sdks.ParseCodeDataJSONResponse(resp, r)
}

func (c *ObjectService) CompleteMultipartUpload(req ObjectCompleteMultipartUpload) (*ObjectCompleteMultipartUploadResp, error) {
	return JSONAPI(c.cfg, http.DefaultClient, &req, &ObjectCompleteMultipartUploadResp{})
}
