package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"sync"

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

const uploadPath = "C:\\Users\\lx\\GolandProjects\\demo\\fileupload\\uploads"
const progressFilePath = "C:\\Users\\lx\\GolandProjects\\demo\\fileupload\\uploads\\progress"

var mu sync.Mutex // 使用互斥锁来确保并发时的进度文件读写安全

func main() {
	router := gin.Default()
	// 确保上传和进度记录目录存在
	err := os.MkdirAll(uploadPath, os.ModePerm)
	if err != nil {
		panic(err)
	}
	err = os.MkdirAll(progressFilePath, os.ModePerm)
	if err != nil {
		panic(err)
	}

	// 定义分块上传接口，支持断点续传
	router.POST("/upload", upload)

	router.Run(":8080")
}

func upload(c *gin.Context) {
	// 获取上传的文件块
	file, header, err := c.Request.FormFile("file")
	if err != nil {
		c.String(http.StatusBadRequest, "Failed to get file: %s", err.Error())
		return
	}
	defer file.Close()

	// 获取文件块编号和总块数
	chunkIndex := c.PostForm("chunkIndex")   // 当前块编号
	totalChunks := c.PostForm("totalChunks") // 总块数
	fileID := c.PostForm("fileID")           // 文件标识符

	chunkIdx, err := strconv.Atoi(chunkIndex)
	if err != nil {
		c.String(http.StatusBadRequest, "Invalid chunk index")
		return
	}
	totalChunksNum, err := strconv.Atoi(totalChunks)
	if err != nil {
		c.String(http.StatusBadRequest, "Invalid total chunks")
		return
	}

	// 先检查该文件的进度文件，看看该块是否已经上传
	if isChunkUploaded(fileID, chunkIdx) {
		c.String(http.StatusOK, "Chunk %d already uploaded", chunkIdx)
		return
	}

	// 构造临时文件路径
	tempFilePath := filepath.Join(uploadPath, fmt.Sprintf("%s.tmp", fileID))

	// 打开或创建临时文件
	tempFile, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
	if err != nil {
		c.String(http.StatusInternalServerError, "Failed to create temp file: %s", err.Error())
		return
	}
	defer tempFile.Close()

	// 计算文件块的偏移量
	chunkSize := header.Size
	offset := int64(chunkIdx) * chunkSize

	// 定位文件指针到正确的偏移量并写入块数据
	_, err = tempFile.Seek(offset, 0)
	if err != nil {
		c.String(http.StatusInternalServerError, "Failed to seek in temp file: %s", err.Error())
		return
	}

	// 将块写入临时文件
	_, err = io.Copy(tempFile, file)
	if err != nil {
		c.String(http.StatusInternalServerError, "Failed to write to temp file: %s", err.Error())
		return
	}

	// 记录该块已上传
	recordChunkProgress(fileID, chunkIdx)

	// 当所有块上传完毕时，将临时文件重命名为最终的文件
	if chunkIdx == totalChunksNum-1 {
		tempFile.Close()
		finalFilePath := filepath.Join(uploadPath, header.Filename)
		err = os.Rename(tempFilePath, finalFilePath)
		if err != nil {
			c.String(http.StatusInternalServerError, "Failed to finalize file: %s", err.Error())
			return
		}

		// 上传完成后删除进度记录文件
		deleteProgressFile(fileID)

		c.String(http.StatusOK, "Upload complete: %s", finalFilePath)
	} else {
		c.String(http.StatusOK, "Chunk %d of %d uploaded", chunkIdx+1, totalChunksNum)
	}
}

// 检查该文件的某个块是否已经上传
func isChunkUploaded(fileID string, chunkIndex int) bool {
	progressFile := filepath.Join(progressFilePath, fileID+".progress")

	mu.Lock()
	defer mu.Unlock()

	if _, err := os.Stat(progressFile); os.IsNotExist(err) {
		return false
	}

	file, err := os.Open(progressFile)
	if err != nil {
		return false
	}
	defer file.Close()

	// 读取进度文件中的内容
	var uploadedChunk int
	for {
		_, err := fmt.Fscanf(file, "%d\n", &uploadedChunk)
		if err != nil {
			break
		}
		if uploadedChunk == chunkIndex {
			return true
		}
	}

	return false
}

// 记录某个文件的某块已经上传
func recordChunkProgress(fileID string, chunkIndex int) {
	progressFile := filepath.Join(progressFilePath, fileID+".progress")

	mu.Lock()
	defer mu.Unlock()

	file, err := os.OpenFile(progressFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm)
	if err != nil {
		fmt.Println("Failed to open progress file:", err)
		return
	}
	defer file.Close()

	_, err = fmt.Fprintf(file, "%d\n", chunkIndex)
	if err != nil {
		fmt.Println("Failed to write to progress file:", err)
	}
}

// 删除进度文件（在文件上传完成后）
func deleteProgressFile(fileID string) {
	progressFile := filepath.Join(progressFilePath, fileID+".progress")
	mu.Lock()
	defer mu.Unlock()
	os.Remove(progressFile)
}
