package api

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"time"

	v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/google/go-querystring/query"
	clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
)

type PresignedService struct {
	*Client
}

func (c *Client) Presigned() *PresignedService {
	return &PresignedService{
		Client: c,
	}
}

const PresignedObjectListByPathPath = "/v1/presigned/object/listByPath"

type PresignedObjectListByPath struct {
	ObjectListByPath
}

func (c *PresignedService) ObjectListByPath(req PresignedObjectListByPath, expireIn int) (string, error) {
	return c.presign(req, PresignedObjectListByPathPath, http.MethodGet, expireIn)
}

const PresignedObjectDownloadByPathPath = "/v1/presigned/object/downloadByPath"

type PresignedObjectDownloadByPath struct {
	PackageID clitypes.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 (c *PresignedService) ObjectDownloadByPath(req PresignedObjectDownloadByPath, expireIn int) (string, error) {
	return c.presign(req, PresignedObjectDownloadByPathPath, http.MethodGet, expireIn)
}

const PresignedObjectDownloadPath = "/v1/presigned/object/download"

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

func (c *PresignedService) ObjectDownload(req PresignedObjectDownload, expireIn int) (string, error) {
	return c.presign(req, PresignedObjectDownloadPath, http.MethodGet, expireIn)
}

const PresignedObjectUploadPath = "/v1/presigned/object/upload"

type PresignedObjectUpload struct {
	PackageID  clitypes.PackageID     `form:"packageID" binding:"required" url:"packageID"`
	Path       string                 `form:"path" binding:"required" url:"path"`
	Affinity   clitypes.UserSpaceID   `form:"affinity" url:"affinity,omitempty"`
	LoadTo     []clitypes.UserSpaceID `form:"loadTo" url:"loadTo,omitempty"`
	LoadToPath []string               `form:"loadToPath" url:"loadToPath,omitempty"`
}

type PresignedObjectUploadResp struct {
	Object clitypes.Object `json:"object"`
}

func (c *PresignedService) ObjectUpload(req PresignedObjectUpload, expireIn int) (string, error) {
	return c.presign(req, PresignedObjectUploadPath, http.MethodPost, expireIn)
}

const PresignedObjectNewMultipartUploadPath = "/v1/presigned/object/newMultipartUpload"

type PresignedObjectNewMultipartUpload struct {
	PackageID clitypes.PackageID `form:"packageID" binding:"required" url:"packageID"`
	Path      string             `form:"path" binding:"required" url:"path"`
}

type PresignedObjectNewMultipartUploadResp struct {
	Object clitypes.Object `json:"object"`
}

func (c *PresignedService) ObjectNewMultipartUpload(req PresignedObjectNewMultipartUpload, expireIn int) (string, error) {
	return c.presign(req, PresignedObjectNewMultipartUploadPath, http.MethodPost, expireIn)
}

const PresignedObjectUploadPartPath = "/v1/presigned/object/uploadPart"

type PresignedObjectUploadPart struct {
	ObjectID clitypes.ObjectID `form:"objectID" binding:"required" url:"objectID"`
	Index    int               `form:"index" binding:"required" url:"index"`
}

type PresignedUploadPartResp struct{}

func (c *PresignedService) ObjectUploadPart(req PresignedObjectUploadPart, expireIn int) (string, error) {
	return c.presign(req, PresignedObjectUploadPartPath, http.MethodPost, expireIn)
}

const PresignedObjectCompleteMultipartUploadPath = "/v1/presigned/object/completeMultipartUpload"

type PresignedObjectCompleteMultipartUpload struct {
	ObjectID clitypes.ObjectID `form:"objectID" binding:"required" url:"objectID"`
	Indexes  []int             `form:"indexes" binding:"required" url:"indexes"`
}

type PresignedObjectCompleteMultipartUploadResp struct {
	Object clitypes.Object `json:"object"`
}

func (c *PresignedService) ObjectCompleteMultipartUpload(req PresignedObjectCompleteMultipartUpload, expireIn int) (string, error) {
	return c.presign(req, PresignedObjectCompleteMultipartUploadPath, http.MethodPost, expireIn)
}

func (c *PresignedService) presign(req any, path string, method string, expireIn int) (string, error) {
	u, err := url.Parse(c.cfg.URL)
	if err != nil {
		return "", err
	}
	u = u.JoinPath(path)

	us, err := query.Values(req)
	if err != nil {
		return "", err
	}
	us.Add("X-Expires", fmt.Sprintf("%v", expireIn))

	u.RawQuery = us.Encode()

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

	r, err := http.NewRequest(method, u.String(), nil)
	if err != nil {
		return "", err
	}

	signer := v4.NewSigner()
	signedURL, _, err := signer.PresignHTTP(context.Background(), cred, r, "", AuthService, AuthRegion, time.Now())
	return signedURL, err
}
