package controller

import (
	"archive/zip"
	"dashboard/utils"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

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

type FileInfo struct {
	Name    string    `json:"name"`
	IsDir   bool      `json:"is_dir"`
	Size    int64     `json:"size"`
	ModTime time.Time `json:"mod_time"`
}

func FileUploadHandle(c *gin.Context) error {
	log, _ := utils.GetLogFromContext(c)

	file, err := c.FormFile("file")
	if err != nil {
		log.Sugar().Errorf("Failed to get file: %v", err)
		return err
	}

	targetDir := c.PostForm("target_dir")
	if targetDir == "" {
		log.Sugar().Errorf("Target directory not specified")
		return errors.New("Target directory not specified")
	}

	// 防止路径穿越攻击
	cleanDir := filepath.Clean(targetDir)
	if strings.Contains(cleanDir, "..") || !strings.HasPrefix(cleanDir, "/") {
		log.Sugar().Errorf("Invalid target directory: %s", cleanDir)
		return errors.New("Invalid target directory")
	}

	// 限制文件大小（10MB）
	const maxFileSize = 100 << 20 // 100MB
	if file.Size > maxFileSize {
		log.Sugar().Errorf("File too large: %d bytes", file.Size)
		return errors.New("File size exceeds 10MB")
	}

	// 确保目标目录存在
	if err := os.MkdirAll(cleanDir, 0755); err != nil {
		log.Sugar().Errorf("Failed to create directory %s: %v", cleanDir, err)
		return err
	}

	// 保存文件
	targetPath := filepath.Join(cleanDir, file.Filename)
	if err := c.SaveUploadedFile(file, targetPath); err != nil {
		log.Sugar().Errorf("Failed to save file to %s: %v", targetPath, err)
		return err
	}

	log.Sugar().Debugf("File uploaded successfully to %s", targetPath)

	c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("File uploaded to %s", targetPath)})

	return nil
}

func FileListHandle(c *gin.Context) error {
	log, _ := utils.GetLogFromContext(c)

	dirPath := c.Query("path")
	if dirPath == "" {
		log.Sugar().Errorf("Directory path not specified")
		return errors.New("Directory path not specified")
	}

	// 清理路径并规范化
	cleanPath := filepath.Clean(dirPath)
	if !strings.HasPrefix(cleanPath, "/") {
		log.Sugar().Errorf("Invalid directory path (not absolute): %s", cleanPath)
		return errors.New("Invalid directory path")
	}

	// 防止路径穿越攻击
	if strings.Contains(cleanPath, "/../") {
		log.Sugar().Errorf("Invalid directory path (contains ..): %s", cleanPath)
		return errors.New("Invalid directory path")
	}

	// 读取目录
	entries, err := os.ReadDir(cleanPath)
	if err != nil {
		log.Sugar().Errorf("Failed to read directory %s: %v", cleanPath, err)
		return err
	}

	var files []FileInfo
	for _, entry := range entries {
		info, err := entry.Info()
		if err != nil {
			log.Sugar().Errorf("Failed to get info for %s: %v", entry.Name(), err)
			continue
		}
		files = append(files, FileInfo{
			Name:    entry.Name(),
			IsDir:   entry.IsDir(),
			Size:    info.Size(),
			ModTime: info.ModTime(),
		})
	}

	log.Sugar().Debugf("Listed files in %s: %d entries", cleanPath, len(files))

	c.JSON(http.StatusOK, gin.H{"files": files})

	return nil
}

func FileDownloadHandle(c *gin.Context) error {
	log, _ := utils.GetLogFromContext(c)

	filePath := c.Query("path")
	if filePath == "" {
		log.Sugar().Errorf("File path not specified")
		return errors.New("File path not specified")
	}

	// 清理路径并规范化
	cleanPath := filepath.Clean(filePath)
	if !strings.HasPrefix(cleanPath, "/") {
		log.Sugar().Errorf("Invalid file path (not absolute): %s", cleanPath)
		return errors.New("Invalid file path")
	}

	// 防止路径穿越攻击
	if strings.Contains(cleanPath, "/../") {
		log.Sugar().Errorf("Invalid file path (contains ..): %s", cleanPath)
		return errors.New("Invalid file path")
	}

	// 检查路径是否存在
	fileInfo, err := os.Stat(cleanPath)
	if err != nil {
		log.Sugar().Errorf("Path not found %s: %v", cleanPath, err)
		return errors.New("Path not found")
	}

	if !fileInfo.IsDir() {
		// 处理文件下载
		const maxFileSize = 100 << 20 // 100MB
		if fileInfo.Size() > maxFileSize {
			log.Sugar().Errorf("File too large: %d bytes", fileInfo.Size())
			return errors.New("File size exceeds 10MB")
		}

		c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filepath.Base(cleanPath)))
		c.Header("Content-Type", "application/octet-stream")
		c.File(cleanPath)

		return nil
	}

	// 处理目录下载（创建ZIP）
	tmpFile, err := os.CreateTemp("", "dir-*.zip")
	if err != nil {
		log.Sugar().Errorf("Failed to create temp file: %v", err)
		return errors.New("Failed to create zip file")
	}
	defer os.Remove(tmpFile.Name()) // 清理临时文件

	zipWriter := zip.NewWriter(tmpFile)
	totalSize := int64(0)
	const maxZipSize = 100 << 20 // 100MB

	err = filepath.Walk(cleanPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// 创建ZIP中的文件头
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}

		// 设置ZIP中的相对路径
		relPath, err := filepath.Rel(cleanPath, path)
		if err != nil {
			return err
		}
		header.Name = relPath

		if info.IsDir() {
			header.Name += "/"
		} else {
			header.Method = zip.Deflate
			totalSize += info.Size()
			if totalSize > maxZipSize {
				return fmt.Errorf("directory size exceeds 100MB")
			}
		}

		writer, err := zipWriter.CreateHeader(header)
		if err != nil {
			return err
		}

		if !info.IsDir() {
			file, err := os.Open(path)
			if err != nil {
				return err
			}
			defer file.Close()
			_, err = io.Copy(writer, file)
			if err != nil {
				return err
			}
		}

		return nil
	})

	if err != nil {
		zipWriter.Close()
		log.Sugar().Errorf("Failed to create zip for %s: %v", cleanPath, err)
		return err
	}

	if err := zipWriter.Close(); err != nil {
		log.Sugar().Errorf("Failed to close zip writer: %v", err)
		return err
	}

	// 设置下载头
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filepath.Base(cleanPath)+".zip"))
	c.Header("Content-Type", "application/zip")
	c.File(tmpFile.Name())

	log.Sugar().Debugf("Directory %s downloaded as zip", cleanPath)

	return nil
}
