package filesystem

import (
	"archive/zip"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// ZipFileEntry 表示 zip 文件中的一个文件条目
type ZipFileEntry struct {
	Name     string
	Path     string // 在 zip 中的完整路径
	Size     int64
	Modified string
	IsDir    bool
	Method   string // 压缩方法 (Store/Deflate)
}

// validateZipPath 验证 ZIP 文件路径是否有效
// 统一的路径验证逻辑，避免在多个函数中重复
func validateZipPath(zipPath string) error {
	if !isSafePath(zipPath) {
		return fmt.Errorf("zip 路径不安全")
	}

	// 检查 zip 文件是否存在
	if _, err := os.Stat(zipPath); os.IsNotExist(err) {
		return fmt.Errorf("zip 文件不存在")
	}

	return nil
}

// debugLog 条件日志记录，仅在调试模式下输出
// 通过设置环境变量 UDESK_ZIP_DEBUG=1 启用调试日志
var zipDebugMode = os.Getenv("UDESK_ZIP_DEBUG") == "1"

func debugLog(format string, args ...interface{}) {
	if zipDebugMode {
		log.Printf(format, args...)
	}
}

// ListZipContents 列出 zip 文件内容
// 🔒 安全增强：添加ZIP炸弹防护、路径遍历检查
func ListZipContents(zipPath string) ([]map[string]interface{}, error) {
	debugLog("[ListZipContents] 开始处理 ZIP 文件: %s", zipPath)

	// 统一验证路径
	if err := validateZipPath(zipPath); err != nil {
		debugLog("[ListZipContents] 路径验证失败: %v", err)
		return nil, err
	}

	// 检查文件是否存在
	fileInfo, err := os.Stat(zipPath)
	if err != nil {
		debugLog("[ListZipContents] 文件状态检查失败: %v", err)
		return nil, fmt.Errorf("无法访问文件: %v", err)
	}

	debugLog("[ListZipContents] 文件信息: 大小=%d bytes, 权限=%v", fileInfo.Size(), fileInfo.Mode())

	// 🔒 安全检查：检查文件大小（太小或太大）
	if fileInfo.Size() < 22 {
		debugLog("[ListZipContents] 文件太小，可能不是有效的 ZIP 文件: %d bytes", fileInfo.Size())
		return nil, fmt.Errorf("文件太小 (%d bytes)，可能不是有效的 ZIP 文件", fileInfo.Size())
	}

	// 🔒 安全检查：ZIP炸弹防护（检查文件大小）
	if fileInfo.Size() > MaxZipSize {
		debugLog("[ListZipContents] ZIP文件过大: %d bytes", fileInfo.Size())
		return nil, fmt.Errorf("ZIP文件过大 (%d bytes)，超过限制 (%d bytes)", fileInfo.Size(), MaxZipSize)
	}

	// 检查文件是否可读
	file, err := os.Open(zipPath)
	if err != nil {
		debugLog("[ListZipContents] 无法打开文件: %v", err)
		return nil, fmt.Errorf("无法打开文件: %v", err)
	}

	// 读取前 4 个字节检查 ZIP 文件头
	header := make([]byte, 4)
	n, err := file.Read(header)
	file.Close()
	if err != nil || n != 4 {
		debugLog("[ListZipContents] 无法读取文件头: n=%d, err=%v", n, err)
		return nil, fmt.Errorf("无法读取文件头")
	}

	debugLog("[ListZipContents] 文件头: 0x%02x 0x%02x 0x%02x 0x%02x", header[0], header[1], header[2], header[3])

	// ZIP 文件应该是 PK\x03\x04 或 PK\x05\x06 (空 ZIP)
	if header[0] != 0x50 || header[1] != 0x4B { // 'P' 'K'
		debugLog("[ListZipContents] 文件头签名错误，不是有效的 ZIP 文件")
		return nil, fmt.Errorf("文件头签名错误，不是有效的 ZIP 文件 (可能是其他格式或已损坏)")
	}

	// 打开 zip 文件
	debugLog("[ListZipContents] 尝试打开 ZIP 读取器...")
	reader, err := zip.OpenReader(zipPath)
	if err != nil {
		debugLog("[ListZipContents] 打开 ZIP 失败: %v", err)
		debugLog("[ListZipContents] 错误类型: %T", err)

		// 提供更详细的错误信息和解决建议
		errMsg := fmt.Sprintf("打开 zip 文件失败: %v", err)
		if strings.Contains(err.Error(), "not a valid zip file") {
			errMsg += "\n\n可能的原因:\n" +
				"1. 文件已损坏或不完整\n" +
				"2. 不是标准的 ZIP 格式\n" +
				"3. 文件正在被其他程序占用（如压缩软件）\n" +
				"4. 使用了特殊的压缩方式\n\n" +
				"建议解决方法:\n" +
				"- 关闭所有可能打开该文件的程序\n" +
				"- 尝试用 7-Zip 或 WinRAR 重新压缩\n" +
				"- 检查文件大小是否正常（不是 0 字节）\n" +
				"- 如果是从网络下载的，尝试重新下载"
		}
		return nil, fmt.Errorf("%s", errMsg)
	}
	defer reader.Close()

	debugLog("[ListZipContents] 成功打开 ZIP，开始读取文件列表...")

	// 🔒 安全检查：ZIP炸弹防护（检查解压后总大小）
	var totalUncompressed int64
	for _, file := range reader.File {
		totalUncompressed += int64(file.UncompressedSize64)
	}
	if totalUncompressed > MaxExtractSize {
		debugLog("[ListZipContents] 解压后总大小过大: %d bytes", totalUncompressed)
		return nil, fmt.Errorf("解压后总大小过大 (%d bytes)，超过限制 (%d bytes)", totalUncompressed, MaxExtractSize)
	}

	var result []map[string]interface{}
	fileCount := 0
	dirCount := 0

	// 遍历 zip 文件中的所有文件
	for _, file := range reader.File {
		// 跳过 macOS 资源分支文件
		if strings.HasPrefix(file.Name, "__MACOSX/") {
			continue
		}

		// 🔒 安全检查：路径遍历攻击防护
		if strings.Contains(file.Name, "..") {
			debugLog("[ListZipContents] 检测到路径遍历尝试: %s", file.Name)
			return nil, fmt.Errorf("ZIP文件包含不安全的路径: %s", file.Name)
		}

		// 🔒 安全检查：绝对路径防护
		if filepath.IsAbs(file.Name) {
			debugLog("[ListZipContents] 检测到绝对路径: %s", file.Name)
			return nil, fmt.Errorf("ZIP文件包含绝对路径: %s", file.Name)
		}

		isDir := file.Mode().IsDir()
		name := filepath.Base(file.Name)

		// 对于目录，使用目录名；对于文件，使用文件名
		if isDir {
			name = file.Name
			dirCount++
		} else {
			fileCount++
		}

		// 压缩方法描述
		method := "Store"
		if file.Method == 8 {
			method = "Deflate"
		}

		entry := map[string]interface{}{
			"name":       name,
			"path":       file.Name, // zip 中的完整路径（已使用 /）
			"is_dir":     isDir,
			"size":       file.UncompressedSize64,
			"compressed": file.CompressedSize64,
			"mod_time":   file.Modified.Format("2006-01-02 15:04:05"),
			"method":     method,
		}

		result = append(result, entry)
	}

	debugLog("[ListZipContents] 读取完成: %d 个文件, %d 个目录", fileCount, dirCount)

	return result, nil
}

// ExtractFileFromZip 从 zip 文件中提取单个文件内容
// 优化：使用通用包装器，消除重复代码
func ExtractFileFromZip(zipPath, filePath string) (string, error) {
	result, err := withZipFile(zipPath, filePath, func(file *zip.File) (interface{}, error) {
		// 打开文件
		rc, err := file.Open()
		if err != nil {
			return nil, fmt.Errorf("打开 zip 中的文件失败: %v", err)
		}
		defer rc.Close()

		// 读取内容
		data, err := readAllFromFile(rc)
		if err != nil {
			return nil, fmt.Errorf("读取文件内容失败: %v", err)
		}

		return string(data), nil
	})

	if err != nil {
		return "", err
	}
	return result.(string), nil
}

// ExtractFileFromZipToTemp 从 zip 文件中提取单个文件到临时目录
// 返回临时文件的完整路径
// 适用于提取图片等二进制文件
// 优化：使用通用包装器，消除重复代码
func ExtractFileFromZipToTemp(zipPath, filePath string) (string, error) {
	debugLog("[ExtractFileFromZipToTemp] 开始提取: %s from %s", filePath, zipPath)

	// 启动临时文件清理协程
	go CleanOldTempFiles()

	// 创建临时目录
	tempDir := filepath.Join(os.TempDir(), TempFileDir)
	if err := os.MkdirAll(tempDir, DefaultDirPermissions); err != nil {
		return "", fmt.Errorf("创建临时目录失败: %v", err)
	}

	result, err := withZipFile(zipPath, filePath, func(file *zip.File) (interface{}, error) {
		// 安全检查：文件大小限制
		if file.UncompressedSize64 > MaxSingleFileSize {
			debugLog("[ExtractFileFromZipToTemp] 文件过大: %d bytes", file.UncompressedSize64)
			return nil, fmt.Errorf("文件过大 (%d bytes)，超过限制 (%d bytes)",
				file.UncompressedSize64, MaxSingleFileSize)
		}

		// 打开文件
		rc, err := file.Open()
		if err != nil {
			return nil, fmt.Errorf("打开 zip 中的文件失败: %v", err)
		}
		defer rc.Close()

		// 生成临时文件名
		tempFileName := fmt.Sprintf("%d_%s", time.Now().UnixNano(), filepath.Base(file.Name))
		tempFilePath := filepath.Join(tempDir, tempFileName)

		// 创建临时文件
		outFile, err := os.Create(tempFilePath)
		if err != nil {
			return nil, fmt.Errorf("创建临时文件失败: %v", err)
		}
		defer outFile.Close()

		// 限制写入大小
		limitedReader := &io.LimitedReader{R: rc, N: MaxSingleFileSize}
		written, err := io.Copy(outFile, limitedReader)
		if err != nil {
			os.Remove(tempFilePath)
			return nil, fmt.Errorf("写入临时文件失败: %v", err)
		}

		// 检查是否超过限制
		if limitedReader.N <= 0 {
			os.Remove(tempFilePath)
			return nil, fmt.Errorf("文件大小超过限制")
		}

		debugLog("[ExtractFileFromZipToTemp] 提取成功: %s -> %s (%d bytes)",
			file.Name, tempFilePath, written)
		return tempFilePath, nil
	})

	if err != nil {
		return "", err
	}
	return result.(string), nil
}

// CleanOldTempFiles 清理超过指定时间的临时文件
// 🔒 新增：防止临时文件累积占用磁盘空间
func CleanOldTempFiles() {
	tempDir := filepath.Join(os.TempDir(), "u-desk-zip")

	// 检查临时目录是否存在
	dir, err := os.Open(tempDir)
	if err != nil {
		// 目录不存在或其他错误，无需清理
		return
	}
	defer dir.Close()

	// 读取目录内容
	files, err := dir.Readdir(-1)
	if err != nil {
		return
	}

	cleanedCount := 0
	now := time.Now()

	for _, file := range files {
		// 跳过目录
		if file.IsDir() {
			continue
		}

		// 检查文件年龄
		if now.Sub(file.ModTime()) > TempFileCleanupAge {
			filePath := filepath.Join(tempDir, file.Name())
			if err := os.Remove(filePath); err == nil {
				cleanedCount++
				debugLog("[CleanOldTempFiles] 清理临时文件: %s (年龄: %v)", file.Name(), now.Sub(file.ModTime()))
			}
		}
	}

	if cleanedCount > 0 {
		debugLog("[CleanOldTempFiles] 清理完成: 共清理 %d 个临时文件", cleanedCount)
	}
}

// GetZipFileInfo 获取 zip 文件中特定文件的信息
// 优化：使用通用包装器，消除重复代码
func GetZipFileInfo(zipPath, filePath string) (map[string]interface{}, error) {
	result, err := withZipFile(zipPath, filePath, func(file *zip.File) (interface{}, error) {
		return createFileInfoMap(file, true), nil
	})

	if err != nil {
		return nil, err
	}
	return result.(map[string]interface{}), nil
}


