package file

import (
	"bytes"
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"fonchain-fiee/api/files"
	"fonchain-fiee/pkg/service"
	"fonchain-fiee/pkg/service/bundle/common"
	"io"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

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.Param("path"))
	if err != nil {
		service.Error(ctx, errors.New(common.FileListFailed))
		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"))
	page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
	pageSize, _ := strconv.Atoi(ctx.DefaultQuery("pageSize", "100000"))
	fileName := ctx.DefaultQuery("fileName", "")
	resp, err := service.FilesProvider.List(ctx, &files.FileListReq{
		Path:          path,
		UserSpacePath: getUserSpacePath(ctx),
		Sorting: &files.Sorting{
			By:  sortBy,
			Asc: sortAsc,
		},
		Page:     int32(page),
		PageSize: int32(pageSize),
		FileName: fileName,
	})
	if err != nil {
		service.Error(ctx, errors.New(common.FileListFailed))
		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, errors.New(common.FileUsageFailed))
		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, errors.New(common.GetFileInfoFailed))
		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, errors.New(common.CreateFileFailed))
		return
	}
	service.Success(ctx, resp)
}

// 文件夹路径保护函数
func isProtectedVideoPath(path string) bool {
	path = strings.TrimSuffix(path, "/")
	if path == "/fiee" || path == "/fiee/video" || path == "/fiee/video/old" {
		return true
	}
	parts := strings.Split(strings.Trim(path, "/"), "/")
	if len(parts) < 3 || parts[0] != "fiee" || parts[1] != "video" {
		return false
	}
	// /fiee/video/<year> — 任意年
	if len(parts) == 3 {
		year, err := strconv.Atoi(parts[2])
		return err == nil && year >= 1000 && year <= 9999
	}
	// /fiee/video/<year>/<year>-<month> — 任意月
	if len(parts) == 4 {
		year, errYear := strconv.Atoi(parts[2])
		if errYear != nil || year < 1000 || year > 9999 {
			return false
		}
		monthPart := parts[3]
		idx := strings.Index(monthPart, "-")
		if idx <= 0 {
			return false
		}
		y, _ := strconv.Atoi(monthPart[:idx])
		m, _ := strconv.Atoi(monthPart[idx+1:])
		return y == year && m >= 1 && m <= 12
	}
	// /fiee/video/<year>/<year>-<month>/<year>-<month>-<day> — 任意日
	if len(parts) == 5 {
		year, errYear := strconv.Atoi(parts[2])
		if errYear != nil || year < 1000 || year > 9999 {
			return false
		}
		monthPart := parts[3]
		idx := strings.Index(monthPart, "-")
		if idx <= 0 {
			return false
		}
		y, _ := strconv.Atoi(monthPart[:idx])
		m, _ := strconv.Atoi(monthPart[idx+1:])
		if y != year || m < 1 || m > 12 {
			return false
		}
		dayPart := parts[4] // <year>-<month>-<day>
		daySegs := strings.Split(dayPart, "-")
		if len(daySegs) != 3 {
			return false
		}
		dy, _ := strconv.Atoi(daySegs[0])
		dm, _ := strconv.Atoi(daySegs[1])
		dd, errDay := strconv.Atoi(daySegs[2])
		return errDay == nil && dy == year && dm == m && dd >= 1 && dd <= 31
	}
	return false
}

func Delete(ctx *gin.Context) {
	path := ctx.DefaultQuery("path", "/")
	if isProtectedVideoPath(path) {
		service.Error(ctx, errors.New("无法删除该目录"))
		return
	}
	resp, err := service.FilesProvider.Delete(ctx, &files.DeleteReq{
		Path:          ctx.DefaultQuery("path", "/"),
		UserSpacePath: getUserSpacePath(ctx),
	})
	if err != nil {
		service.Error(ctx, errors.New(common.DeleteFileFailed))
		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, errors.New(common.SearchFileFailed))
		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, errors.New(common.ERROR_OPEN_FILE))
		return
	}
	resp, err := service.FilesProvider.Upload(ctx, &files.UploadReq{
		Path:          path,
		UserSpacePath: getUserSpacePath(ctx),
		Content:       b,
	})
	if err != nil {
		service.Error(ctx, errors.New(common.UploadFileFailed))
		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, errors.New(common.TusCreateFailed))
		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, errors.New(common.ERROR_OPEN_FILE))
		return
	}
	mf, err := ctx.MultipartForm()
	if err != nil {
		service.Error(ctx, errors.New(common.ERROR_OPEN_FILE))
		return
	}
	f, err := mf.File["file"][0].Open()
	if err != nil {
		service.Error(ctx, errors.New(common.ERROR_OPEN_FILE))
		return
	}
	defer f.Close()
	b, err := io.ReadAll(f)
	// b, err := io.ReadAll(ctx.Request.Body)
	if !ok {
		service.Error(ctx, errors.New(common.ERROR_OPEN_FILE))
		return
	}
	if uploadOffset == 0 && len(b) > 0 {
		if strings.HasSuffix(strings.ToLower(path), ".mp4") {
			if err != nil {
				service.Error(ctx, errors.New(common.ERROR_OPEN_FILE))
				return
			}

			if !isLikelyMP4(b) {
				service.Error(ctx, errors.New(".mp4机器码校验错误,该文件可能不是标准的.mp4文件"))
				//删除对应空文件
				_, err = service.FilesProvider.Delete(ctx, &files.DeleteReq{
					Path:          ctx.DefaultQuery("path", "/"),
					UserSpacePath: getUserSpacePath(ctx),
				})
				if err != nil {
					service.Error(ctx, errors.New(common.DeleteFileFailed))
					return
				}
				return
			}
		}
	}
	resp, err := service.FilesProvider.TusUpload(ctx, &files.TusUploadReq{
		Path:          path,
		UploadOffset:  uploadOffset,
		Content:       b,
		UserSpacePath: getUserSpacePath(ctx),
	})
	if err != nil {
		service.Error(ctx, errors.New(common.TusUploadFailed))
		return
	}
	ctx.Writer.Header().Set("Upload-Offset", strconv.FormatInt(resp.UploadOffset, 10))
	service.Success(ctx, nil)
}

func TusUploadMP4(ctx *gin.Context) {
	//读取传入的本地路径文件
	var req files.TusCreateReq
	if err := ctx.ShouldBindJSON(&req); err != nil {
		service.Error(ctx, err)
		return
	}
	userPath := req.UserSpacePath
	req.UserSpacePath = getUserSpacePath(ctx)
	fileInfo, err := os.Stat(userPath)
	if err != nil {
		service.Error(ctx, errors.New(common.GetFileInfoFailed))
		return
	}
	if fileInfo.IsDir() {
		service.Error(ctx, errors.New("localPath 不能是目录"))
		return
	}
	totalSize := fileInfo.Size()
	if totalSize <= 0 {
		service.Error(ctx, errors.New("文件为空"))
		return
	}
	//如果是.mp4文件则判断文件大小
	if strings.HasSuffix(strings.ToLower(req.Path), ".mp4") && fileInfo.Size() > 1024*1024 {
		//大于1mb的文件进行分块上传
		f, err := os.Open(userPath)
		if err != nil {
			service.Error(ctx, errors.New(common.ERROR_OPEN_FILE))
			return
		}
		defer f.Close()
		const chunkSize int64 = 2*1024*1024 - 100

		for offset := int64(0); offset < totalSize; {
			end := offset + chunkSize
			if end > totalSize {
				end = totalSize
			}
			n := end - offset
			buf := make([]byte, n)
			// 用 ReadAt 读指定偏移的分片
			readN, readErr := f.ReadAt(buf, offset)
			if readErr != nil && readErr != io.EOF {
				service.Error(ctx, errors.New(common.ERROR_OPEN_FILE))
				return
			}
			if int64(readN) != n {
				service.Error(ctx, errors.New("读取分片长度不一致"))
				return
			}
			// 4) 首包 MP4 机器码校验（offset==0）
			if offset == 0 {
				if !isLikelyMP4(buf) {
					service.Error(ctx, errors.New("mp4机器码校验失败,该文件可能不是标准 mp4"))
					return
				}
			}
			// 5) TusUpload 写入当前分片
			_, err = service.FilesProvider.TusUpload(ctx, &files.TusUploadReq{
				Path:          req.Path,
				UploadOffset:  offset,
				Content:       buf,
				UserSpacePath: req.UserSpacePath,
			})
			if err != nil {
				service.Error(ctx, errors.New(common.TusUploadFailed))
				return
			}
			offset = end
		}
		service.Success(ctx, gin.H{
			"destPath":     req.Path,
			"localPath":    userPath,
			"uploadLength": totalSize,
		})
	}
}

func Preview(ctx *gin.Context) {
	var size int
	size, err := strconv.Atoi(ctx.Param("size"))
	if err != nil {
		size = 1
	}
	resp, err := service.FilesProvider.Preview(ctx, &files.PreviewReq{
		Path:          ctx.Param("path"),
		UserSpacePath: getUserSpacePath(ctx),
		Size:          uint32(size),
	})
	if err != nil {
		service.Error(ctx, errors.New(common.PreviewFileFailed))
		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, errors.New(common.ActionFailed))
		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, errors.New(common.DirDownloadFailed))
		return
	}
	header, err := stream.Header()
	if err != nil {
		service.Error(ctx, errors.New(common.DirDownloadFailed))
		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, errors.New(common.InvalidUploadOffset)
	}
	return uploadOffset, nil
}

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

	return ""
}

func SecurityScan(ctx *gin.Context) {
	var req files.SecurityScanReq
	if err := ctx.ShouldBindJSON(&req); err != nil {
		service.Error(ctx, err)
		return
	}

	go func() {
		// 使用独立的 context，避免原请求 context 被取消
		scanCtx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
		defer cancel()
		// 执行安全扫描

		_, err := service.FilesProvider.SecurityScan(scanCtx, &req)
		if err != nil {
			// 记录错误日志
			zap.L().Error("安全扫描失败",
				zap.String("url", req.Url),
				zap.String("fileName", req.FileName),
				zap.Error(err))
		} else {
			zap.L().Info("安全扫描完成",
				zap.String("url", req.Url),
				zap.String("fileName", req.FileName))
		}
	}()
	service.Success(ctx, gin.H{
		"message": "安全扫描任务已提交，正在后台处理",
	})
}

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

func ManualAnti(ctx *gin.Context) {
	var req files.ManualAntiReq
	if err := ctx.ShouldBindJSON(&req); err != nil {
		service.Error(ctx, err)
		return
	}
	go func() {
		resp, err := service.FilesProvider.ManualAnti(ctx, &req)
		if err != nil {
			service.Error(ctx, err)
			return
		}
		service.Success(ctx, resp)
	}()
	service.Success(ctx, gin.H{
		"message": "手动反制任务已提交，正在后台处理",
	})
}

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

func isLikelyMP4(b []byte) bool {
	fmt.Println("开始效验机器码")
	if len(b) < 16 {
		fmt.Println("len(b) < 16", len(b))

		return false
	}
	limit := 2048
	if len(b) < limit {
		limit = len(b)
	}
	ftyp := []byte("ftyp")
	idx := bytes.Index(b[:limit], ftyp)
	if idx < 4 { // idx-4 处需要是 size
		return false
	}
	if idx+12 > len(b) { // ftyp(4) + major_brand(4) + minor_version(4) 需要至少覆盖到 major
		return false
	}
	// box size: b[idx-4 : idx] (big-endian)
	size := binary.BigEndian.Uint32(b[idx-4 : idx])
	if size < 16 {
		return false
	}
	major := string(b[idx+4 : idx+8]) // major_brand
	switch major {
	case "isom", "iso2", "iso6", "mp41", "mp42", "avc1", "dash":
		return true
	default:
		return false
	}
}
