package utils

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

type FileInfo struct {
	Version      string `json:"version"`
	Extension    string `json:"extension"`
	FileName     string `json:"fileName"`
	DownloadPath string `json:"downloadPath"`
	Size         string `json:"size"`
	SizeBytes    int64  `json:"sizeBytes"`
	UpdateDate   string `json:"updateDate"`
	UpdateTime   string `json:"updateTime"`
}

type ProductsInfo map[string][]FileInfo

var (
	supportedPackageExtOrder = []string{
		".tar.gz",
		".appimage",
		".msi",
		".exe",
		".zip",
		".pkg",
		".dmg",
		".apk",
		".deb",
		".rpm",
	}
	versionPattern = regexp.MustCompile(`(?i)(?:^|[ _-])v?(\d+(?:\.\d+){1,4})(?:$|[ _-])`)
)

func FormatBytes(bytes int64, precision int) string {
	if bytes == 0 {
		return "0 B"
	}

	units := []string{"B", "KB", "MB", "GB", "TB"}
	if precision == 0 {
		precision = 2
	}

	bytesFloat := float64(bytes)
	if bytesFloat < 0 {
		bytesFloat = 0
	}

	pow := 0
	if bytesFloat > 0 {
		pow = int(float64(len(fmt.Sprintf("%.0f", bytesFloat))-1) / 3.321928)
	}
	if pow >= len(units) {
		pow = len(units) - 1
	}

	divisor := int64(1)
	for i := 0; i < pow; i++ {
		divisor *= 1024
	}

	converted := bytesFloat / float64(divisor)
	return fmt.Sprintf("%.*f %s", precision, converted, units[pow])
}

func GetProductsInfo(downloadDir string, logger *Logger) ProductsInfo {
	products := make(ProductsInfo)

	if _, err := os.Stat(downloadDir); os.IsNotExist(err) {
		logger.Error(fmt.Sprintf("读取下载目录失败: %s - 错误: %s", downloadDir, err.Error()))
		return nil
	}

	entries, err := os.ReadDir(downloadDir)
	if err != nil {
		logger.Error(fmt.Sprintf("读取下载目录失败: %s - 错误: %s", downloadDir, err.Error()))
		return nil
	}

	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}

		fileName := entry.Name()
		productName, version, ext, ok := parsePackageFileName(fileName)
		if !ok {
			continue
		}

		filePath := filepath.Join(downloadDir, fileName)
		stat, err := os.Stat(filePath)
		if err != nil {
			logger.Warn(fmt.Sprintf("获取文件统计信息失败: %s - 错误: %s", fileName, err.Error()))
			continue
		}

		fileInfoData := FileInfo{
			Version:      version,
			Extension:    strings.TrimPrefix(ext, "."),
			FileName:     fileName,
			DownloadPath: "/downloads/" + fileName,
			Size:         FormatBytes(stat.Size(), 2),
			SizeBytes:    stat.Size(),
			UpdateDate:   stat.ModTime().Format("2006-01-02"),
			UpdateTime:   stat.ModTime().Format("2006-01-02 15:04:05"),
		}

		products[productName] = append(products[productName], fileInfoData)
	}

	for productName := range products {
		sort.Slice(products[productName], func(i, j int) bool {
			v1 := products[productName][i].Version
			v2 := products[productName][j].Version
			if compareVersions(v1, v2) == 0 {
				return products[productName][i].UpdateTime > products[productName][j].UpdateTime
			}
			return compareVersions(v1, v2) > 0
		})
	}

	return products
}

func GetLatestProductRelease(products ProductsInfo, preferredProduct string) (string, *FileInfo) {
	if len(products) == 0 {
		return "", nil
	}

	var selectedProduct string
	var selectedRelease *FileInfo

	if preferredProduct != "" {
		for productName, releases := range products {
			if !IsSameProduct(productName, preferredProduct) || len(releases) == 0 {
				continue
			}
			candidate := releases[0]
			if selectedRelease == nil || isNewerRelease(candidate, *selectedRelease) {
				selectedProduct = productName
				selectedRelease = &candidate
			}
		}
		if selectedRelease != nil {
			return selectedProduct, selectedRelease
		}
	}

	for productName, releases := range products {
		if len(releases) == 0 {
			continue
		}
		candidate := releases[0]
		if selectedRelease == nil || isNewerRelease(candidate, *selectedRelease) {
			selectedProduct = productName
			selectedRelease = &candidate
		}
	}

	return selectedProduct, selectedRelease
}

func IsSameProduct(productName string, preferredProduct string) bool {
	return productAliasKey(productName) == productAliasKey(preferredProduct)
}

func productAliasKey(name string) string {
	key := strings.ToLower(strings.TrimSpace(name))
	key = strings.NewReplacer(" ", "", "_", "", "-", "", ".", "").Replace(key)
	switch key {
	case "ymhutbox", "ymhut":
		return "ymhutbox"
	default:
		return key
	}
}

func isNewerRelease(candidate FileInfo, current FileInfo) bool {
	versionCompare := compareVersions(candidate.Version, current.Version)
	if versionCompare != 0 {
		return versionCompare > 0
	}
	return candidate.UpdateTime > current.UpdateTime
}

func parsePackageFileName(fileName string) (string, string, string, bool) {
	ext, stem, ok := splitPackageExtension(fileName)
	if !ok {
		return "", "", "", false
	}

	version := extractVersion(stem)
	productName := normalizeProductName(stem, version)
	if productName == "" {
		productName = stem
	}

	if version == "" {
		version = "未标注"
	}

	return productName, version, ext, true
}

func splitPackageExtension(fileName string) (string, string, bool) {
	lower := strings.ToLower(fileName)
	for _, ext := range supportedPackageExtOrder {
		if strings.HasSuffix(lower, ext) {
			return ext, fileName[:len(fileName)-len(ext)], true
		}
	}
	return "", "", false
}

func extractVersion(stem string) string {
	matches := versionPattern.FindStringSubmatch(stem)
	if len(matches) > 1 {
		return matches[1]
	}
	return ""
}

func normalizeProductName(stem string, version string) string {
	name := stem
	if version != "" {
		name = versionPattern.ReplaceAllString(name, " ")
	}

	replacements := []string{
		"setup", "installer", "install", "release", "portable",
		"windows", "window", "win", "linux", "android", "macos", "darwin",
		"x64", "x86", "arm64", "amd64", "universal", "desktop",
	}

	for _, token := range replacements {
		re := regexp.MustCompile(`(?i)(^|[ _-])` + regexp.QuoteMeta(token) + `($|[ _-])`)
		name = re.ReplaceAllString(name, " ")
	}

	name = strings.NewReplacer("_", " ", "-", " ").Replace(name)
	name = strings.Join(strings.Fields(name), " ")
	return strings.TrimSpace(name)
}

func compareVersions(v1, v2 string) int {
	if v1 == "未标注" && v2 == "未标注" {
		return 0
	}
	if v1 == "未标注" {
		return -1
	}
	if v2 == "未标注" {
		return 1
	}

	parts1 := strings.Split(v1, ".")
	parts2 := strings.Split(v2, ".")

	maxLen := len(parts1)
	if len(parts2) > maxLen {
		maxLen = len(parts2)
	}

	for i := 0; i < maxLen; i++ {
		num1 := 0
		num2 := 0
		if i < len(parts1) {
			fmt.Sscanf(parts1[i], "%d", &num1)
		}
		if i < len(parts2) {
			fmt.Sscanf(parts2[i], "%d", &num2)
		}

		if num1 > num2 {
			return 1
		}
		if num1 < num2 {
			return -1
		}
	}

	return 0
}

func FileExists(filePath string) bool {
	_, err := os.Stat(filePath)
	return !os.IsNotExist(err)
}

func ReadJSONFile(filePath string) (map[string]interface{}, error) {
	data, err := os.ReadFile(filePath)
	if err != nil {
		return nil, err
	}

	var result map[string]interface{}
	if err := json.Unmarshal(data, &result); err != nil {
		return nil, err
	}

	return result, nil
}

func GetMimeType(filePath string) string {
	ext := strings.ToLower(filepath.Ext(filePath))

	mimeTypes := map[string]string{
		".json":  "application/json; charset=utf-8",
		".ttf":   "font/ttf",
		".otf":   "font/otf",
		".woff":  "font/woff",
		".woff2": "font/woff2",
		".css":   "text/css; charset=utf-8",
		".js":    "application/javascript; charset=utf-8",
		".png":   "image/png",
		".jpg":   "image/jpeg",
		".jpeg":  "image/jpeg",
		".gif":   "image/gif",
		".svg":   "image/svg+xml",
		".ico":   "image/x-icon",
		".html":  "text/html; charset=utf-8",
		".txt":   "text/plain; charset=utf-8",
		".exe":   "application/octet-stream",
		".zip":   "application/zip",
		".pkg":   "application/octet-stream",
		".dmg":   "application/x-apple-diskimage",
		".msi":   "application/x-msi",
		".apk":   "application/vnd.android.package-archive",
		".deb":   "application/vnd.debian.binary-package",
		".rpm":   "application/x-rpm",
	}

	if mime, ok := mimeTypes[ext]; ok {
		return mime
	}

	if strings.HasSuffix(strings.ToLower(filePath), ".tar.gz") {
		return "application/gzip"
	}

	return "application/octet-stream"
}
