package handler

import (
	"errors"
	"kra/internal/service"
	"kra/internal/service/dto"
	"net/http"
	"strconv"

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

type Media struct{ service *service.MediaService }

func NewMedia(service *service.MediaService) *Media { return &Media{service: service} }

func (h *Media) limitMultipartBody(c *gin.Context) int64 {
	limit := h.service.MediaConfig().EffectiveMaxFileSize()
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, limit+(1<<20))
	return limit
}

func rejectMediaTooLarge(c *gin.Context, err error, message string) bool {
	var tooLarge *http.MaxBytesError
	if !errors.As(err, &tooLarge) {
		return false
	}
	c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, Response{Code: CodeError, Data: gin.H{}, Msg: message})
	return true
}

func (h *Media) Upload(c *gin.Context) {
	claims := Claims(c)
	if claims == nil {
		Fail(c, "接收文件失败")
		return
	}
	limit := h.limitMultipartBody(c)
	header, err := c.FormFile("file")
	if err != nil {
		if rejectMediaTooLarge(c, err, "文件超过大小上限") {
			return
		}
		Fail(c, "接收文件失败")
		return
	}
	if header.Size > limit {
		c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, Response{Code: CodeError, Data: gin.H{}, Msg: "文件超过大小上限"})
		return
	}
	opened, err := header.Open()
	if err != nil {
		// Let the upload service surface this as the generic upload
		// failure; keep the endpoint message consistent.
		Fail(c, "上传文件失败")
		return
	}
	defer opened.Close()
	category, _ := strconv.Atoi(c.DefaultPostForm("classId", "0"))
	save := c.DefaultQuery("noSave", "0") == "0"
	item, err := h.service.Upload(c.Request.Context(), claims.ID, header.Filename, header.Header.Get("Content-Type"), category, opened, save)
	if err != nil {
		Fail(c, "上传文件失败")
		return
	}
	Write(c, CodeSuccess, gin.H{"file": item}, "上传成功")
}

func (h *Media) List(c *gin.Context) {
	var req dto.MediaListRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	items, total, err := h.service.MediaListRequest(c.Request.Context(), &req)
	if err != nil {
		Fail(c, "获取失败")
		return
	}
	Write(c, CodeSuccess, PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
}
func (h *Media) Delete(c *gin.Context) {
	var req dto.IDRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	if err := h.service.Delete(c.Request.Context(), req.ID); err != nil {
		Fail(c, "删除失败")
		return
	}
	Write(c, CodeSuccess, gin.H{}, "删除成功")
}
func (h *Media) DeleteMany(c *gin.Context) {
	var req dto.MediaBatchDeleteRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	failed := []uint{}
	for _, id := range req.IDs {
		if h.service.Delete(c.Request.Context(), id) != nil {
			failed = append(failed, id)
		}
	}
	if len(failed) > 0 {
		Write(c, CodeSuccess, gin.H{"failedIds": failed}, "部分文件删除失败")
		return
	}
	Write(c, CodeSuccess, gin.H{}, "批量删除成功")
}
func (h *Media) Find(c *gin.Context) {
	id, parseErr := strconv.ParseUint(c.Query("id"), 10, 64)
	if parseErr != nil {
		Fail(c, "文件ID非法")
		return
	}
	item, err := h.service.Media(c.Request.Context(), uint(id))
	if err != nil {
		Fail(c, "查询失败")
		return
	}
	OKWithData(c, item)
}
func (h *Media) Rename(c *gin.Context) {
	var req dto.RenameMediaRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	if err := h.service.Rename(c.Request.Context(), req.ID, req.Name); err != nil {
		Fail(c, "编辑失败")
		return
	}
	Write(c, CodeSuccess, gin.H{}, "编辑成功")
}
func (h *Media) ImportURLs(c *gin.Context) {
	var req []dto.ImportMediaRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	if err := h.service.ImportURLRequests(c.Request.Context(), req); err != nil {
		Fail(c, "导入URL失败")
		return
	}
	Write(c, CodeSuccess, gin.H{}, "导入URL成功")
}
func (h *Media) Storage(c *gin.Context) {
	var req dto.StorageListRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	items, next, more, err := h.service.Storage(c.Request.Context(), req.Prefix, req.Cursor, req.Limit)
	if err != nil {
		Fail(c, "列举存储桶文件失败")
		return
	}
	Write(c, CodeSuccess, gin.H{"list": items, "nextCursor": next, "hasMore": more}, "获取成功")
}

func (h *Media) Categories(c *gin.Context) {
	items, err := h.service.Categories(c.Request.Context())
	if err != nil {
		Fail(c, "获取分类列表失败")
		return
	}
	OKWithData(c, items)
}
func (h *Media) SaveCategory(c *gin.Context) {
	var req dto.CategoryRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, "参数错误")
		return
	}
	if err := h.service.SaveCategoryRequest(c.Request.Context(), &req); err != nil {
		Fail(c, "创建/更新失败："+err.Error())
		return
	}
	Write(c, CodeSuccess, gin.H{}, "创建/更新成功")
}
func (h *Media) DeleteCategory(c *gin.Context) {
	var req dto.IDRequest
	if c.ShouldBindJSON(&req) != nil || req.ID == 0 {
		Fail(c, "参数错误")
		return
	}
	if err := h.service.DeleteCategory(c.Request.Context(), req.ID); err != nil {
		Fail(c, "删除失败")
		return
	}
	Write(c, CodeSuccess, gin.H{}, "删除成功")
}

func (h *Media) InitUpload(c *gin.Context) {
	claims := Claims(c)
	var req dto.InitUploadRequest
	if claims == nil {
		Fail(c, "参数错误")
		return
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	if config := h.service.MediaConfig(); config.MaxFileSize > 0 && req.FileSize > config.MaxFileSize {
		Fail(c, "文件超过大小上限")
		return
	}
	result, err := h.service.InitUpload(c.Request.Context(), claims.ID, req.FileName, req.FileHash, req.FileSize, req.ChunkSize, req.ChunkTotal)
	if err != nil {
		Fail(c, err.Error())
		return
	}
	Write(c, CodeSuccess, result, "成功")
}
func (h *Media) SaveChunk(c *gin.Context) {
	claims := Claims(c)
	if claims == nil {
		Fail(c, "接收分片失败")
		return
	}
	limit := h.limitMultipartBody(c)
	header, err := c.FormFile("chunk")
	if err != nil {
		if rejectMediaTooLarge(c, err, "分片超过大小上限") {
			return
		}
		Fail(c, "接收分片失败")
		return
	}
	if header.Size > limit {
		c.AbortWithStatusJSON(http.StatusRequestEntityTooLarge, Response{Code: CodeError, Data: gin.H{}, Msg: "分片超过大小上限"})
		return
	}
	uploadID, parseErr := strconv.ParseUint(c.PostForm("uploadId"), 10, 64)
	if parseErr != nil || uploadID == 0 {
		Fail(c, "上传会话 ID 非法")
		return
	}
	index, parseErr := strconv.Atoi(c.PostForm("chunkIndex"))
	if parseErr != nil || index < 0 {
		Fail(c, "分片序号非法")
		return
	}
	opened, err := header.Open()
	if err != nil {
		Fail(c, "分片读取失败")
		return
	}
	defer opened.Close()
	if err = h.service.SaveChunk(c.Request.Context(), claims.ID, uint(uploadID), index, c.PostForm("chunkHash"), opened); err != nil {
		Fail(c, err.Error())
		return
	}
	Write(c, CodeSuccess, gin.H{}, "ok")
}
func (h *Media) CompleteUpload(c *gin.Context) {
	claims := Claims(c)
	var req dto.CompleteUploadRequest
	if claims == nil {
		Fail(c, "参数错误")
		return
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, err.Error())
		return
	}
	item, err := h.service.CompleteUpload(c.Request.Context(), claims.ID, req.UploadID)
	if err != nil {
		Fail(c, err.Error())
		return
	}
	Write(c, CodeSuccess, gin.H{"media": item}, "成功")
}
func (h *Media) CancelUpload(c *gin.Context) {
	claims := Claims(c)
	id, _ := strconv.ParseUint(c.Param("uploadId"), 10, 64)
	if claims == nil {
		Fail(c, "未登录")
		return
	}
	if err := h.service.CancelUpload(c.Request.Context(), claims.ID, uint(id)); err != nil {
		Fail(c, err.Error())
		return
	}
	Write(c, CodeSuccess, gin.H{}, "已取消")
}
