package file

import (
	"bytes"
	"errors"
	"fmt"
	"fonchain-fiee/api/files"
	"fonchain-fiee/pkg/service"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

func Raw(ctx *gin.Context) {
	r := ctx.Request
	w := ctx.Writer

	w.Header().Add("Content-Security-Policy", `script-src 'none';`)
	w.Header().Set("Cache-Control", "private")
	rs, err := newGrpcReaderSeeker(getUserSpacePath(ctx), ctx.Query("path"))
	if err != nil {
		service.Error(ctx, err)
		return
	}
	if r.URL.Query().Get("inline") == "true" {
		w.Header().Set("Content-Disposition", "inline")
	} else {
		// As per RFC6266 section 4.3
		w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+rs.FileName)
	}
	http.ServeContent(ctx.Writer, r, rs.FileName, time.Now(), rs)
}

func List(ctx *gin.Context) {
	path := ctx.DefaultQuery("path", "/")
	sortBy := ctx.DefaultQuery("sortBy", "name")
	sortAsc, _ := strconv.ParseBool(ctx.DefaultQuery("sortAsc", "true"))
	resp, err := service.FilesProvider.List(ctx, &files.FileListReq{
		Path:          path,
		UserSpacePath: getUserSpacePath(ctx),
		Sorting: &files.Sorting{
			By:  sortBy,
			Asc: sortAsc,
		},
	})
	if err != nil {
		service.Error(ctx, err)
		return
	}
	service.Success(ctx, resp)
}

func Usage(ctx *gin.Context) {
	path := ctx.DefaultQuery("path", "/")
	resp, err := service.FilesProvider.Usage(ctx, &files.UsageReq{
		Path:          path,
		UserSpacePath: getUserSpacePath(ctx),
	})
	if err != nil {
		service.Error(ctx, err)
		return
	}
	service.Success(ctx, resp)
}

func Info(ctx *gin.Context) {
	resp, err := service.FilesProvider.Info(ctx, &files.FileInfoReq{
		Path:          ctx.DefaultQuery("path", "/"),
		UserSpacePath: getUserSpacePath(ctx),
	})
	if err != nil {
		service.Error(ctx, err)
		return
	}
	service.Success(ctx, resp)
}

func Create(ctx *gin.Context) {
	var req files.CreateReq
	if err := ctx.ShouldBindJSON(&req); err != nil {
		service.Error(ctx, err)
		return
	}
	req.UserSpacePath = getUserSpacePath(ctx)
	resp, err := service.FilesProvider.Create(ctx, &req)
	if err != nil {
		service.Error(ctx, err)
		return
	}
	service.Success(ctx, resp)
}

func Delete(ctx *gin.Context) {
	resp, err := service.FilesProvider.Delete(ctx, &files.DeleteReq{
		Path:          ctx.DefaultQuery("path", "/"),
		UserSpacePath: getUserSpacePath(ctx),
	})
	if err != nil {
		service.Error(ctx, err)
		return
	}
	service.Success(ctx, resp)
}

func Search(ctx *gin.Context) {
	resp, err := service.FilesProvider.Search(ctx, &files.SearchReq{
		UserSpacePath: getUserSpacePath(ctx),
		Path:          ctx.Query("path"),
		Query:         ctx.Query("query"),
	})
	if err != nil {
		service.Error(ctx, err)
		return
	}
	service.Success(ctx, resp)
}

func Upload(ctx *gin.Context) {
	path, ok := ctx.GetQuery("path")
	if !ok {
		service.Error(ctx, errors.New("缺失参数路径"))
		return
	}
	b, err := io.ReadAll(ctx.Request.Body)
	if !ok {
		service.Error(ctx, err)
		return
	}
	resp, err := service.FilesProvider.Upload(ctx, &files.UploadReq{
		Path:          path,
		UserSpacePath: getUserSpacePath(ctx),
		Content:       b,
	})
	if err != nil {
		service.Error(ctx, err)
		return
	}
	service.Success(ctx, resp)
}

func TusCreate(ctx *gin.Context) {
	var req files.TusCreateReq
	if err := ctx.ShouldBindJSON(&req); err != nil {
		service.Error(ctx, err)
		return
	}
	req.UserSpacePath = getUserSpacePath(ctx)
	resp, err := service.FilesProvider.TusCreate(ctx, &req)
	if err != nil {
		service.Error(ctx, err)
		return
	}
	service.Success(ctx, resp)
}

func TusUpload(ctx *gin.Context) {
	path, ok := ctx.GetQuery("path")
	if !ok {
		service.Error(ctx, errors.New("文件路径缺失"))
		return
	}
	uploadOffset, err := getUploadOffset(ctx.Request)
	if err != nil {
		service.Error(ctx, fmt.Errorf("invalid upload offset: %w", err))
		return
	}
	b, err := io.ReadAll(ctx.Request.Body)
	if !ok {
		service.Error(ctx, err)
		return
	}
	resp, err := service.FilesProvider.TusUpload(ctx, &files.TusUploadReq{
		Path:          path,
		UploadOffset:  uploadOffset,
		Content:       b,
		UserSpacePath: getUserSpacePath(ctx),
	})
	if err != nil {
		service.Error(ctx, err)
		return
	}
	ctx.Writer.Header().Set("Upload-Offset", strconv.FormatInt(resp.UploadOffset, 10))
	service.Success(ctx, nil)
}

func Preview(ctx *gin.Context) {
	var size int
	size, err := strconv.Atoi(ctx.Query("size"))
	if err != nil {
		size = 1
	}
	resp, err := service.FilesProvider.Preview(ctx, &files.PreviewReq{
		Path:          ctx.Query("path"),
		UserSpacePath: getUserSpacePath(ctx),
		Size:          uint32(size),
	})
	if err != nil {
		service.Error(ctx, err)
		return
	}
	ctx.Writer.Header().Set("Cache-Control", "private")
	http.ServeContent(ctx.Writer, ctx.Request, resp.FileName, time.UnixMilli(resp.ModTime), bytes.NewReader(resp.Content))
}

func Action(ctx *gin.Context) {
	var req files.ActionReq
	if err := ctx.ShouldBindJSON(&req); err != nil {
		service.Error(ctx, err)
		return
	}
	req.UserSpacePath = getUserSpacePath(ctx)
	resp, err := service.FilesProvider.Action(ctx, &req)
	if err != nil {
		service.Error(ctx, err)
		return
	}
	service.Success(ctx, resp)
}

func DirDownload(ctx *gin.Context) {
	path := ctx.Query("path")
	fileList := strings.Split(ctx.Query("files"), ",")
	algo := ctx.Query("algo")
	stream, err := service.FilesProvider.DirDownload(ctx, &files.DirDownloadReq{
		Algo:          algo,
		Files:         fileList,
		Path:          path,
		UserSpacePath: getUserSpacePath(ctx),
	})
	if err != nil {
		service.Error(ctx, err)
		return
	}
	header, err := stream.Header()
	if err != nil {
		service.Error(ctx, err)
		return
	}

	ctx.Writer.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(header.Get("filename")[0]))
	for {
		recvMsg, err := stream.Recv()
		if err != nil {
			break
		}
		ctx.Writer.Write(recvMsg.Content)
	}
}

func getUploadOffset(r *http.Request) (int64, error) {
	uploadOffset, err := strconv.ParseInt(r.Header.Get("Upload-Offset"), 10, 64)
	if err != nil {
		return 0, fmt.Errorf("invalid upload offset: %w", err)
	}
	return uploadOffset, nil
}

func getUserSpacePath(ctx *gin.Context) string {
	// user := login.GetUserInfoFromC(ctx)
	// return strconv.Itoa(int(user.ID))

	return ""
}