/*
* @desc:上传处理
* @company:云南奇讯科技有限公司
* @Author: yixiaohu<yxh669@qq.com>
* @Date:   2022/9/28 9:37
 */

package upload

import (
	"context"
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/text/gregex"
	"github.com/gogf/gf/v2/text/gstr"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/tiger1103/gfast/v3/internal/app/common/consts"
	"github.com/tiger1103/gfast/v3/internal/app/common/model"
	"github.com/tiger1103/gfast/v3/internal/app/common/model/entity"
	"github.com/tiger1103/gfast/v3/internal/app/common/service"
	"github.com/tiger1103/gfast/v3/library/libUtils"
	"github.com/tiger1103/gfast/v3/library/liberr"
	"github.com/tiger1103/gfast/v3/library/upload"
	"io"
)

func init() {
	service.RegisterUpload(New())
}

func New() service.IUpload {
	return &sUpload{}
}

type sUpload struct{}

// UploadFiles 上传多文件
func (s *sUpload) UploadFiles(ctx context.Context, files []*ghttp.UploadFile, checkFileType string, source int, userId uint64, appId string) (result []*model.UploadResponse, err error) {
	for _, item := range files {
		f, e := s.UploadFile(ctx, item, checkFileType, source, userId, appId)
		if e != nil {
			return
		}
		result = append(result, f)
	}
	return
}

// UploadFile 上传单文件
func (s *sUpload) UploadFile(ctx context.Context,
	file *ghttp.UploadFile, checkFileType string, source int,
	userId uint64, appId string) (result *model.UploadResponse, err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		// 检查文件类型
		err = s.CheckType(ctx, checkFileType, file.Filename)
		liberr.ErrIsNil(ctx, err)

		// 检查文件大小
		err = s.CheckSize(ctx, checkFileType, file.Size)
		liberr.ErrIsNil(ctx, err)
		//判断该文件是否已经上传过，上传过则不再上传
		var (
			md5        = ""
			existsFile *model.SysAttachmentInfoRes
		)
		md5, err = s.computeMD5(file)
		liberr.ErrIsNil(ctx, err)
		existsFile, err = service.SysAttachment().GetByMd5(ctx, md5)
		liberr.ErrIsNil(ctx, err, "获取文件信息失败")
		if existsFile != nil {
			result = &model.UploadResponse{
				Size:     existsFile.Size,
				Path:     existsFile.Path,
				FullPath: libUtils.GetDomain(ctx, true) + "/" + existsFile.Path,
				Name:     existsFile.Name,
				Type:     existsFile.MimeType,
			}
			return
		}
		uploader := upload.GetUploader(upload.UploaderType(source))
		if uploader == nil {
			liberr.ErrIsNil(ctx, errors.New("没有找到上传适配器"))
		}
		result, err = uploader.Upload(ctx, file)
		liberr.ErrIsNil(ctx, err)
		//保存上传文件到数据库
		err = service.SysAttachment().AddUpload(ctx, result, &model.SysAttachmentAddAttribute{
			Md5:    md5,
			Driver: gconv.Uint(source),
			UserId: userId,
			AppId:  appId,
		})
		liberr.ErrIsNil(ctx, err)
	})
	return
}

// CheckSize 检查上传文件大小
func (s *sUpload) CheckSize(ctx context.Context, checkFileType string, fileSize int64) (err error) {

	var (
		configSize *entity.SysConfig
	)

	if checkFileType == consts.CheckFileTypeFile {

		//获取上传大小配置
		configSize, err = s.getUpConfig(ctx, consts.FileSizeKey)
		if err != nil {
			return
		}
	} else if checkFileType == consts.CheckFileTypeImg {

		//获取上传大小配置
		configSize, err = s.getUpConfig(ctx, consts.ImgSizeKey)
		if err != nil {
			return
		}
	} else {
		return errors.New(fmt.Sprintf("文件检查类型错误:%s|%s", consts.CheckFileTypeFile, consts.CheckFileTypeImg))
	}

	var rightSize bool
	rightSize, err = s.checkSize(configSize.ConfigValue, fileSize)
	if err != nil {
		return
	}
	if !rightSize {
		err = gerror.New("上传文件超过最大尺寸：" + configSize.ConfigValue)
		return
	}
	return
}

// CheckType 检查上传文件类型
func (s *sUpload) CheckType(ctx context.Context, checkFileType string, fileName string) (err error) {

	var (
		configType *entity.SysConfig
	)

	if checkFileType == consts.CheckFileTypeFile {
		//获取上传类型配置
		configType, err = s.getUpConfig(ctx, consts.FileTypeKey)
		if err != nil {
			return
		}

	} else if checkFileType == consts.CheckFileTypeImg {
		//获取上传类型配置
		configType, err = s.getUpConfig(ctx, consts.ImgTypeKey)
		if err != nil {
			return
		}
	} else {
		return errors.New(fmt.Sprintf("文件检查类型错误:%s|%s", consts.CheckFileTypeFile, consts.CheckFileTypeImg))
	}

	rightType := s.checkFileType(fileName, configType.ConfigValue)
	if !rightType {
		err = gerror.New("上传文件类型错误，只能包含后缀为：" + configType.ConfigValue + "的文件。")
		return
	}
	return
}

// 获取上传配置
func (s *sUpload) getUpConfig(ctx context.Context, key string) (config *entity.SysConfig, err error) {
	config, err = service.SysConfig().GetConfigByKey(ctx, key)
	if err != nil {
		return
	}
	if config == nil {
		err = gerror.New("上传文件类型未设置，请在后台配置")
		return
	}
	return
}

// 判断上传文件类型是否合法
func (s *sUpload) checkFileType(fileName, typeString string) bool {
	suffix := gstr.SubStrRune(fileName, gstr.PosRRune(fileName, ".")+1, gstr.LenRune(fileName)-1)
	imageType := gstr.Split(typeString, ",")
	rightType := false
	for _, v := range imageType {
		if gstr.Equal(suffix, v) {
			rightType = true
			break
		}
	}
	return rightType
}

// 检查文件大小是否合法
func (s *sUpload) checkSize(configSize string, fileSize int64) (bool, error) {
	match, err := gregex.MatchString(`^([0-9]+)(?i:([a-z]*))$`, configSize)
	if err != nil {
		return false, err
	}
	if len(match) == 0 {
		err = gerror.New("上传文件大小未设置，请在后台配置，格式为（30M,30k,30MB）")
		return false, err
	}
	var cfSize int64
	switch gstr.ToUpper(match[2]) {
	case "MB", "M":
		cfSize = gconv.Int64(match[1]) * 1024 * 1024
	case "KB", "K":
		cfSize = gconv.Int64(match[1]) * 1024
	case "":
		cfSize = gconv.Int64(match[1])
	}
	if cfSize == 0 {
		err = gerror.New("上传文件大小未设置，请在后台配置，格式为（30M,30k,30MB），最大单位为MB")
		return false, err
	}
	return cfSize >= fileSize, nil
}

// 静态文件夹目录
func (s *sUpload) getStaticPath(ctx context.Context) string {
	value, _ := g.Cfg().Get(ctx, "server.serverRoot")
	if !value.IsEmpty() {
		return value.String()
	}
	return ""
}

// computeMD5 计算给定文件的 MD5 值
func (s *sUpload) computeMD5(upFile *ghttp.UploadFile) (string, error) {
	file, err := upFile.Open()
	if err != nil {
		return "", err
	}
	defer file.Close()

	// 创建一个新的 MD5 哈希对象
	hash := md5.New()

	// 逐块读取文件内容并更新哈希
	if _, err := io.Copy(hash, file); err != nil {
		return "", err
	}

	// 将 MD5 值转换为十六进制字符串
	return hex.EncodeToString(hash.Sum(nil)), nil
}

func (s *sUpload) CheckMultipart(ctx context.Context, req *model.CheckMultipartReq) (res *model.CheckMultipartRes, err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		// 检查文件类型
		err = s.CheckType(ctx, req.UploadType, req.FileName)
		liberr.ErrIsNil(ctx, err)

		// 检查文件大小
		err = s.CheckSize(ctx, req.UploadType, req.Size)
		liberr.ErrIsNil(ctx, err)
		//检查文件是否已经上传
		var existsFile *model.SysAttachmentInfoRes
		existsFile, err = service.SysAttachment().GetByMd5(ctx, req.Md5)
		liberr.ErrIsNil(ctx, err, "获取文件信息失败")
		res = new(model.CheckMultipartRes)
		if existsFile != nil {
			res.Attachment = &model.UploadResponse{
				Size:     existsFile.Size,
				Path:     existsFile.Path,
				FullPath: libUtils.GetDomain(ctx, true) + "/" + existsFile.Path,
				Name:     existsFile.Name,
				Type:     existsFile.MimeType,
			}
			return
		}

		uploader := upload.GetUploader(upload.UploaderType(req.DriverType))
		if uploader == nil {
			liberr.ErrIsNil(ctx, errors.New("没有找到上传适配器"))
		}
		res, err = uploader.CheckMultipart(ctx, req)
		liberr.ErrIsNil(ctx, err)
	})
	return
}

func (s *sUpload) UploadPart(ctx context.Context, req *model.UploadPartReq) (res *model.UploadPartRes, err error) {
	err = g.Try(ctx, func(ctx context.Context) {
		uploader := upload.GetUploader(upload.UploaderType(req.DriverType))
		if uploader == nil {
			liberr.ErrIsNil(ctx, errors.New("没有找到上传适配器"))
		}
		res, err = uploader.UploadPart(ctx, req)
		liberr.ErrIsNil(ctx, err)
		//保存上传文件到数据库
		if res != nil && res.Finish {
			err = service.SysAttachment().AddUpload(ctx, res.Attachment, &model.SysAttachmentAddAttribute{
				Md5:    req.Md5,
				Driver: gconv.Uint(req.DriverType),
				UserId: req.UserId,
				AppId:  req.AppId,
			})
			liberr.ErrIsNil(ctx, err)
		}
	})
	return
}
