package admin

import (
	"archive/zip"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"time"
)

// MergePatchWithFullPackage merges a patch with a full package to create a new full package
func MergePatchWithFullPackage(fullPackagePath, patchPath string, appID uint) (string, int64, string, error) {
	fullPackageFile := filepath.Join(".", fullPackagePath)
	patchFile := filepath.Join(".", patchPath)

	tempDir := filepath.Join(os.TempDir(), fmt.Sprintf("merge_%d_%d", appID, time.Now().Unix()))
	if err := os.MkdirAll(tempDir, 0755); err != nil {
		return "", 0, "", fmt.Errorf("create temp dir failed: %v", err)
	}
	defer os.RemoveAll(tempDir)

	extractDir := filepath.Join(tempDir, "extracted")
	if err := os.MkdirAll(extractDir, 0755); err != nil {
		return "", 0, "", fmt.Errorf("create extract dir failed: %v", err)
	}

	if err := extractZip(fullPackageFile, extractDir); err != nil {
		return "", 0, "", fmt.Errorf("extract full package failed: %v", err)
	}

	if err := extractZip(patchFile, extractDir); err != nil {
		return "", 0, "", fmt.Errorf("extract patch failed: %v", err)
	}

	timestamp := time.Now().Unix()
	newFileName := fmt.Sprintf("full_%d_%d.zip", appID, timestamp)
	newFilePath := filepath.Join("uploads", "versions", newFileName)

	if err := os.MkdirAll(filepath.Dir(newFilePath), 0755); err != nil {
		return "", 0, "", fmt.Errorf("create dir failed: %v", err)
	}

	if err := createZipFromDir(extractDir, newFilePath); err != nil {
		return "", 0, "", fmt.Errorf("create new full package failed: %v", err)
	}

	fileInfo, err := os.Stat(newFilePath)
	if err != nil {
		return "", 0, "", fmt.Errorf("stat new file failed: %v", err)
	}

	hash, err := calculateFileHash(newFilePath)
	if err != nil {
		return "", 0, "", fmt.Errorf("calculate hash failed: %v", err)
	}

	log.Printf("[INFO] Created merged full package: %s, size: %d", newFilePath, fileInfo.Size())

	return "/" + filepath.ToSlash(newFilePath), fileInfo.Size(), hash, nil
}

// GenerateDeltaPackage generates a delta package containing only changed/new files
func GenerateDeltaPackage(basePackagePath, newPackagePath string, appID uint) (string, int64, string, error) {
	basePackageFile := filepath.Join(".", basePackagePath)
	newPackageFile := filepath.Join(".", newPackagePath)

	// 获取基础包的文件哈希映射
	baseFiles, err := getZipFileHashes(basePackageFile)
	if err != nil {
		return "", 0, "", fmt.Errorf("read base package failed: %v", err)
	}

	// 获取新包的文件哈希映射
	newFiles, err := getZipFileHashes(newPackageFile)
	if err != nil {
		return "", 0, "", fmt.Errorf("read new package failed: %v", err)
	}

	// 找出差异文件（新增或修改）
	var deltaFiles []string
	for filePath, hash := range newFiles {
		if baseHash, exists := baseFiles[filePath]; !exists || baseHash != hash {
			deltaFiles = append(deltaFiles, filePath)
		}
	}

	if len(deltaFiles) == 0 {
		log.Printf("[INFO] No delta files found, packages are identical")
		return "", 0, "", nil
	}

	log.Printf("[INFO] Found %d delta files", len(deltaFiles))

	// 创建增量包
	timestamp := time.Now().Unix()
	deltaFileName := fmt.Sprintf("delta_%d_%d.zip", appID, timestamp)
	deltaFilePath := filepath.Join("uploads", "versions", deltaFileName)

	if err := os.MkdirAll(filepath.Dir(deltaFilePath), 0755); err != nil {
		return "", 0, "", fmt.Errorf("create dir failed: %v", err)
	}

	// 从新包中提取差异文件并打包
	if err := createDeltaZip(newPackageFile, deltaFilePath, deltaFiles); err != nil {
		return "", 0, "", fmt.Errorf("create delta package failed: %v", err)
	}

	fileInfo, err := os.Stat(deltaFilePath)
	if err != nil {
		return "", 0, "", fmt.Errorf("stat delta file failed: %v", err)
	}

	hash, err := calculateFileHash(deltaFilePath)
	if err != nil {
		return "", 0, "", fmt.Errorf("calculate hash failed: %v", err)
	}

	log.Printf("[INFO] Created delta package: %s, size: %d", deltaFilePath, fileInfo.Size())

	return "/" + filepath.ToSlash(deltaFilePath), fileInfo.Size(), hash, nil
}

// getZipFileHashes returns a map of file paths to their SHA256 hashes
func getZipFileHashes(zipPath string) (map[string]string, error) {
	r, err := zip.OpenReader(zipPath)
	if err != nil {
		return nil, err
	}
	defer r.Close()

	fileHashes := make(map[string]string)
	for _, f := range r.File {
		if f.FileInfo().IsDir() {
			continue
		}

		rc, err := f.Open()
		if err != nil {
			continue
		}

		hash := sha256.New()
		io.Copy(hash, rc)
		rc.Close()

		fileHashes[f.Name] = hex.EncodeToString(hash.Sum(nil))
	}

	return fileHashes, nil
}

// createDeltaZip creates a zip file containing only the specified files from the source zip
func createDeltaZip(sourceZipPath, deltaZipPath string, deltaFiles []string) error {
	r, err := zip.OpenReader(sourceZipPath)
	if err != nil {
		return err
	}
	defer r.Close()

	deltaFileSet := make(map[string]bool)
	for _, f := range deltaFiles {
		deltaFileSet[f] = true
	}

	zipFile, err := os.Create(deltaZipPath)
	if err != nil {
		return err
	}
	defer zipFile.Close()

	zipWriter := zip.NewWriter(zipFile)
	defer zipWriter.Close()

	for _, f := range r.File {
		if !deltaFileSet[f.Name] {
			continue
		}

		if f.FileInfo().IsDir() {
			continue
		}

		writer, err := zipWriter.Create(f.Name)
		if err != nil {
			return err
		}

		rc, err := f.Open()
		if err != nil {
			return err
		}

		_, err = io.Copy(writer, rc)
		rc.Close()
		if err != nil {
			return err
		}
	}

	return nil
}

func extractZip(zipPath, destDir string) error {
	r, err := zip.OpenReader(zipPath)
	if err != nil {
		return err
	}
	defer r.Close()

	for _, f := range r.File {
		destPath := filepath.Join(destDir, f.Name)

		if f.FileInfo().IsDir() {
			os.MkdirAll(destPath, 0755)
			continue
		}

		os.MkdirAll(filepath.Dir(destPath), 0755)

		destFile, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			return err
		}

		srcFile, err := f.Open()
		if err != nil {
			destFile.Close()
			return err
		}

		_, err = io.Copy(destFile, srcFile)
		srcFile.Close()
		destFile.Close()

		if err != nil {
			return err
		}
	}

	return nil
}

func createZipFromDir(srcDir, zipPath string) error {
	zipFile, err := os.Create(zipPath)
	if err != nil {
		return err
	}
	defer zipFile.Close()

	zipWriter := zip.NewWriter(zipFile)
	defer zipWriter.Close()

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

		relPath, err := filepath.Rel(srcDir, path)
		if err != nil {
			return err
		}

		if info.IsDir() {
			_, err = zipWriter.Create(relPath + "/")
			return err
		}

		writer, err := zipWriter.Create(relPath)
		if err != nil {
			return err
		}

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

		_, err = io.Copy(writer, file)
		return err
	})
}