package server

import (
	"io"
	"net/http"
	"os"
	"strings"

	"github.com/gin-gonic/gin"

	"warpbox/lib/boxstore"
	"warpbox/lib/helpers"
	"warpbox/lib/models"
)

func (app *App) handleCreateBox(ctx *gin.Context) {
	if !app.requireAPI(ctx) || !app.requireGuestUploads(ctx) {
		return
	}
	app.limitRequestBody(ctx)

	boxID, err := boxstore.NewBoxID()
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create upload box"})
		return
	}

	if err := os.MkdirAll(boxstore.BoxPath(boxID), 0755); err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not prepare upload box"})
		return
	}

	var request models.CreateBoxRequest
	if err := ctx.ShouldBindJSON(&request); err != nil && err != io.EOF {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box payload"})
		return
	}
	if err := app.validateCreateBoxRequest(&request); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	totalSize := int64(0)
	for _, file := range request.Files {
		totalSize += file.Size
	}
	if !app.enforceUploadRateLimit(ctx, totalSize) {
		return
	}

	files, err := boxstore.CreateManifest(boxID, request)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "files": files})
}

func (app *App) handleManifestFileUpload(ctx *gin.Context) {
	if !app.requireAPI(ctx) || !app.requireGuestUploads(ctx) {
		return
	}
	app.limitRequestBody(ctx)

	boxID := ctx.Param("id")
	fileID := ctx.Param("file_id")
	if !boxstore.ValidBoxID(boxID) {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box id"})
		return
	}

	file, err := ctx.FormFile("file")
	if err != nil {
		boxstore.MarkFileStatus(boxID, fileID, models.FileStatusFailed)
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "No file received"})
		return
	}
	if err := app.validateManifestFileUpload(boxID, fileID, file.Size); err != nil {
		boxstore.MarkFileStatus(boxID, fileID, models.FileStatusFailed)
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if !app.enforceUploadRateLimit(ctx, file.Size) {
		boxstore.MarkFileStatus(boxID, fileID, models.FileStatusFailed)
		return
	}

	savedFile, err := boxstore.SaveManifestUpload(boxID, fileID, file)
	if err != nil {
		boxstore.MarkFileStatus(boxID, fileID, models.FileStatusFailed)
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "file": savedFile})
}

func (app *App) handleFileStatusUpdate(ctx *gin.Context) {
	if !app.requireAPI(ctx) {
		return
	}
	app.limitRequestBody(ctx)

	boxID := ctx.Param("id")
	fileID := ctx.Param("file_id")
	if !boxstore.ValidBoxID(boxID) || !helpers.ValidLowerHexID(fileID, 16) {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid file"})
		return
	}

	var request models.UpdateFileStatusRequest
	if err := ctx.ShouldBindJSON(&request); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid status payload"})
		return
	}
	if request.Status == models.FileStatusReady {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Uploads must complete through the upload endpoint"})
		return
	}
	if err := app.rejectExpiredManifestBox(boxID); err != nil {
		ctx.JSON(http.StatusGone, gin.H{"error": err.Error()})
		return
	}

	file, err := boxstore.MarkFileStatus(boxID, fileID, request.Status)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	ctx.JSON(http.StatusOK, gin.H{"file": file})
}

func (app *App) handleDirectBoxUpload(ctx *gin.Context) {
	if !app.requireAPI(ctx) || !app.requireGuestUploads(ctx) {
		return
	}
	app.limitRequestBody(ctx)

	boxID := ctx.Param("id")
	if !boxstore.ValidBoxID(boxID) {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box id"})
		return
	}

	file, err := ctx.FormFile("file")
	if err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "No file received"})
		return
	}
	if err := app.validateIncomingFile(boxID, file.Size); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if !app.enforceUploadRateLimit(ctx, file.Size) {
		return
	}

	savedFile, err := boxstore.SaveUpload(boxID, file)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "file": savedFile})
}

func (app *App) handleLegacyUpload(ctx *gin.Context) {
	if !app.requireAPI(ctx) || !app.requireGuestUploads(ctx) {
		return
	}
	app.limitRequestBody(ctx)

	form, err := ctx.MultipartForm()
	if err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "No files received"})
		return
	}

	files := form.File["files"]
	if len(files) == 0 {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "No files received"})
		return
	}
	totalSize := int64(0)
	for _, file := range files {
		if err := app.validateFileSize(file.Size); err != nil {
			ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		totalSize += file.Size
	}
	if err := app.validateBoxSize(totalSize); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if !app.enforceUploadRateLimit(ctx, totalSize) {
		return
	}

	boxID, err := boxstore.NewBoxID()
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create upload box"})
		return
	}

	if err := os.MkdirAll(boxstore.BoxPath(boxID), 0755); err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not prepare upload box"})
		return
	}

	retentionKey := strings.TrimSpace(ctx.PostForm("retention_key"))
	if retentionKey == "" {
		retentionKey = strings.TrimSpace(ctx.PostForm("retention"))
	}
	allowZip := true
	if strings.EqualFold(strings.TrimSpace(ctx.PostForm("allow_zip")), "false") {
		allowZip = false
	}
	request := models.CreateBoxRequest{
		RetentionKey: retentionKey,
		Password:     ctx.PostForm("password"),
		AllowZip:     &allowZip,
		Files:        make([]models.CreateBoxFileRequest, 0, len(files)),
	}
	for _, file := range files {
		request.Files = append(request.Files, models.CreateBoxFileRequest{Name: file.Filename, Size: file.Size})
	}
	if err := app.validateCreateBoxRequest(&request); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	manifestFiles, err := boxstore.CreateManifest(boxID, request)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	savedFiles := make([]models.BoxFile, 0, len(files))
	for index, file := range files {
		savedFile, err := boxstore.SaveManifestUpload(boxID, manifestFiles[index].ID, file)
		if err != nil {
			_, _ = boxstore.MarkFileStatus(boxID, manifestFiles[index].ID, models.FileStatusFailed)
			ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		savedFiles = append(savedFiles, savedFile)
	}

	ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "files": savedFiles})
}
