package config

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strings"

	"software-download-center/database"
	"software-download-center/handlers"
	"software-download-center/middleware"
	"software-download-center/utils"

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

type ProductMeta struct {
	Icon        string
	Description string
	ThemeColor  string
	Tags        []string
}

var (
	productMeta = map[string]ProductMeta{
		"YMhut Box": {
			Icon:        `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>`,
			Description: "多功能工具箱，整合系统、网络、图像和日常效率工具。",
			ThemeColor:  "#166534",
			Tags:        []string{"桌面工具", "效率", "多平台"},
		},
		"YmhutBox": {
			Icon:        `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>`,
			Description: "多功能工具箱，整合系统、网络、图像和日常效率工具。",
			ThemeColor:  "#166534",
			Tags:        []string{"桌面工具", "效率", "多平台"},
		},
		"弓福小筑": {
			Icon:        `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>`,
			Description: "轻量入口应用，提供站点访问与定制下载入口。",
			ThemeColor:  "#b45309",
			Tags:        []string{"轻量", "入口", "桌面"},
		},
	}

	defaultMeta = ProductMeta{
		Icon:        `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2v20"/><path d="M2 12h20"/></svg>`,
		Description: "自动从 downloads 目录识别出的安装包分组。",
		ThemeColor:  "#57534e",
		Tags:        []string{"自动识别", "安装包", "下载"},
	}
)

func RegisterRoutes(r *gin.Engine, logger *utils.Logger) {
	logger.System("\n开始注册路由...\n")

	rootDir, err := os.Getwd()
	if err != nil {
		logger.Error(fmt.Sprintf("获取工作目录失败: %s", err.Error()))
		return
	}

	publicDir := filepath.Join(rootDir, "public")
	viewsDir := filepath.Join(rootDir, "views")
	downloadsDir := filepath.Join(publicDir, "downloads")

	r.SetFuncMap(template.FuncMap{
		"safeHTML": func(s string) template.HTML {
			return template.HTML(s)
		},
		"marshalJSON": func(v interface{}) string {
			data, _ := json.Marshal(v)
			return string(data)
		},
		"slice": func(slice interface{}, start int, args ...int) interface{} {
			v := reflect.ValueOf(slice)
			if v.Kind() != reflect.Slice {
				return slice
			}
			end := v.Len()
			if len(args) > 0 {
				end = args[0]
			}
			if start < 0 {
				start = 0
			}
			if end > v.Len() {
				end = v.Len()
			}
			if start >= end {
				return reflect.MakeSlice(v.Type(), 0, 0).Interface()
			}
			return v.Slice(start, end).Interface()
		},
	})
	r.LoadHTMLGlob(filepath.Join(viewsDir, "*.html"))

	r.GET("/", func(c *gin.Context) {
		products := utils.GetProductsInfo(downloadsDir, logger)

		errorMessage := ""
		if products == nil {
			errorMessage = "无法读取 downloads 目录，请检查目录权限和文件配置。"
		} else if len(products) == 0 {
			errorMessage = "downloads 目录中暂时没有可识别的安装包。"
		}

		c.HTML(http.StatusOK, "index.html", gin.H{
			"products":     products,
			"productMeta":  productMeta,
			"defaultMeta":  defaultMeta,
			"pageTitle":    "YMhut 下载中心",
			"errorMessage": errorMessage,
		})
	})
	logger.Info("注册路由成功 [GET] /")

	registerDynamicUpdateInfoRoutes(r, logger, publicDir, downloadsDir)
	registerReleaseAPIRoutes(r, logger, publicDir, downloadsDir)

	jsonRoutes := []struct {
		path         string
		file         string
		cacheControl string
	}{
		{"/tool-status.json", "tool-status.json", "public, max-age=600"},
		{"/tool-status", "tool-status.json", "public, max-age=600"},
		{"/media-types.json", "media-types.json", "public, max-age=3600"},
		{"/media-types", "media-types.json", "public, max-age=3600"},
		{"/plugins", "plugins.json", "public, max-age=3600"},
		{"/plugins.json", "plugins.json", "public, max-age=3600"},
		{"/modules", "modules.json", "public, max-age=600"},
		{"/modules.json", "modules.json", "public, max-age=600"},
	}

	for _, route := range jsonRoutes {
		filePath := filepath.Join(publicDir, route.file)
		fp := filePath
		cc := route.cacheControl
		path := route.path
		fileName := route.file
		if utils.FileExists(filePath) {
			r.GET(path, func(c *gin.Context) {
				if cached, ok := utils.GetCachedConfig(fileName); ok {
					c.Header("Content-Type", "application/json; charset=utf-8")
					c.Header("Cache-Control", cc)
					c.JSON(http.StatusOK, cached)
					return
				}

				data, err := utils.ReadJSONFile(fp)
				if err != nil {
					logger.Error(fmt.Sprintf("读取 JSON 文件失败: %s - %s", fp, err.Error()))
					c.JSON(http.StatusInternalServerError, gin.H{"error": "文件读取失败"})
					return
				}

				utils.SaveConfig(fileName, data)
				c.Header("Content-Type", "application/json; charset=utf-8")
				c.Header("Cache-Control", cc)
				c.JSON(http.StatusOK, data)
			})
			logger.Info(fmt.Sprintf("注册路由成功 [GET] %s", path))
		} else {
			logger.Warn(fmt.Sprintf("跳过路由，文件不存在: %s", path))
		}
	}

	fileRoutes := []struct {
		path         string
		file         string
		cacheControl string
		headers      map[string]string
	}{
		{"/lang/zh-CN.json", "lang/zh-CN.json", "public, max-age=86400", map[string]string{"Content-Disposition": `attachment; filename="zh-CN.json"`}},
		{"/lang/en-US.json", "lang/en-US.json", "public, max-age=86400", map[string]string{"Content-Disposition": `attachment; filename="en-US.json"`}},
		{"/fonts/MeiGanShouXieTi-2.ttf", "fonts/MeiGanShouXieTi-2.ttf", "public, max-age=86400", map[string]string{"Content-Disposition": `attachment; filename="MeiGanShouXieTi-2.ttf"`}},
		{"/fonts/QianTuBiFengShouXieTi-2.ttf", "fonts/QianTuBiFengShouXieTi-2.ttf", "public, max-age=86400", map[string]string{"Content-Disposition": `attachment; filename="QianTuBiFengShouXieTi-2.ttf"`}},
		{"/fonts/YOzBS-2.otf", "fonts/YOzBS-2.otf", "public, max-age=86400", map[string]string{"Content-Disposition": `attachment; filename="YOzBS-2.otf"`}},
		{"/favicon.ico", "img/favicon.png", "public, max-age=604800", nil},
	}

	for _, route := range fileRoutes {
		filePath := filepath.Join(publicDir, route.file)
		fp := filePath
		cc := route.cacheControl
		hdrs := route.headers
		path := route.path
		if utils.FileExists(filePath) {
			r.GET(path, func(c *gin.Context) {
				mimeType := utils.GetMimeType(fp)
				c.Header("Content-Type", mimeType)
				c.Header("Cache-Control", cc)
				if hdrs != nil {
					for k, v := range hdrs {
						c.Header(k, v)
					}
				}
				c.File(fp)
			})
			logger.Info(fmt.Sprintf("注册路由成功 [GET] %s", path))
		} else {
			logger.Warn(fmt.Sprintf("跳过路由，文件不存在: %s", path))
		}
	}

	r.Static("/css", filepath.Join(publicDir, "css"))
	r.Static("/img", filepath.Join(publicDir, "img"))

	r.GET("/downloads/:filename", func(c *gin.Context) {
		filename := c.Param("filename")
		if strings.Contains(filename, "..") || strings.Contains(filename, "/") || strings.Contains(filename, "\\") {
			logger.Warn(fmt.Sprintf("拒绝下载请求: %s (IP: %s)", filename, c.ClientIP()))
			c.JSON(http.StatusForbidden, gin.H{"error": "禁止访问"})
			return
		}

		filePath := filepath.Join(downloadsDir, filename)
		resolvedPath, err := filepath.Abs(filePath)
		if err != nil {
			logger.Error(fmt.Sprintf("解析文件路径失败: %s", err.Error()))
			c.JSON(http.StatusInternalServerError, gin.H{"error": "内部错误"})
			return
		}

		normalizedDir, _ := filepath.Abs(downloadsDir)
		if !strings.HasPrefix(resolvedPath, normalizedDir) {
			logger.Warn(fmt.Sprintf("下载路径越界: %s (IP: %s)", filename, c.ClientIP()))
			c.JSON(http.StatusForbidden, gin.H{"error": "禁止访问"})
			return
		}

		if !utils.FileExists(filePath) {
			logger.Error(fmt.Sprintf("下载失败，文件不存在: %s", filename))
			c.HTML(http.StatusNotFound, "404.html", gin.H{
				"title": "文件未找到",
				"path":  c.Request.URL.String(),
			})
			return
		}

		logger.Info(fmt.Sprintf("下载成功: %s (IP: %s)", filename, c.ClientIP()))
		c.File(filePath)
	})

	r.GET("/admin", middleware.AuthMiddleware(), func(c *gin.Context) {
		c.HTML(http.StatusOK, "admin.html", gin.H{"title": "后台管理"})
	})

	r.GET("/admin/login", func(c *gin.Context) {
		if token, _ := c.Cookie("token"); token != "" {
			c.Redirect(http.StatusFound, "/admin")
			return
		}
		c.HTML(http.StatusOK, "login.html", gin.H{"title": "登录"})
	})

	r.GET("/admin/register", func(c *gin.Context) {
		if token, _ := c.Cookie("token"); token != "" {
			c.Redirect(http.StatusFound, "/admin")
			return
		}
		c.HTML(http.StatusOK, "register.html", gin.H{"title": "注册"})
	})

	r.GET("/admin/settings", middleware.AuthMiddleware(), middleware.AdminMiddleware(), func(c *gin.Context) {
		c.HTML(http.StatusOK, "settings.html", gin.H{"title": "系统设置"})
	})

	r.GET("/admin/install", func(c *gin.Context) {
		if database.IsDBInitialized() {
			c.Redirect(http.StatusFound, "/admin/login")
			return
		}
		c.HTML(http.StatusOK, "install.html", gin.H{"title": "数据库配置"})
	})

	r.GET("/admin/install/status", handlers.CheckInstallStatus)
	r.POST("/admin/install/database", handlers.InstallDatabase)

	adminRoutes := r.Group("/admin")
	{
		adminRoutes.POST("/register", handlers.Register)
		adminRoutes.POST("/login", handlers.Login)
		adminRoutes.POST("/logout", handlers.Logout)
		adminRoutes.GET("/me", middleware.AuthMiddleware(), handlers.GetCurrentUser)

		adminAPI := adminRoutes.Group("/api")
		adminAPI.Use(middleware.AuthMiddleware(), middleware.AdminMiddleware())
		{
			adminAPI.GET("/logs", handlers.GetLogs)
			adminAPI.GET("/routes", handlers.GetRoutes)
			adminAPI.POST("/routes", handlers.CreateRoute)
			adminAPI.PUT("/routes/:id", handlers.UpdateRoute)
			adminAPI.DELETE("/routes/:id", handlers.DeleteRoute)

			adminAPI.GET("/files", handlers.GetFiles)
			adminAPI.GET("/file", handlers.ReadFile)
			adminAPI.POST("/file", handlers.SaveFile)

			adminAPI.PUT("/config", handlers.UpdateJSONConfig)
			adminAPI.GET("/system", handlers.GetSystemInfo)

			adminAPI.GET("/database", handlers.GetDatabaseInfo)
			adminAPI.GET("/database/config", handlers.GetDatabaseConfig)
			adminAPI.POST("/database/config", handlers.UpdateDatabaseConfig)
			adminAPI.POST("/database/convert", handlers.ConvertDatabase)
			adminAPI.POST("/database/password", handlers.UpdateDatabasePassword)

			adminAPI.POST("/reload", handlers.ReloadRoutes)
		}
	}

	r.NoRoute(func(c *gin.Context) {
		fullURL := c.Request.URL.String()
		logger.Warn(fmt.Sprintf("404 Not Found - %s", fullURL))
		c.HTML(http.StatusNotFound, "404.html", gin.H{
			"title": "页面未找到",
			"path":  fullURL,
		})
	})

	r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
		logger.Error(fmt.Sprintf("500 Server Error - 路径: %s - 错误: %v", c.Request.URL.Path, recovered))
		c.HTML(http.StatusInternalServerError, "500.html", gin.H{
			"title":   "服务器错误",
			"message": "服务器内部错误，请稍后重试。",
		})
		c.Abort()
	}))

	logger.System("路由注册完成。\n")
}

func registerDynamicUpdateInfoRoutes(
	r *gin.Engine,
	logger *utils.Logger,
	publicDir string,
	downloadsDir string,
) {
	updateInfoPath := filepath.Join(publicDir, "update-info.json")
	for _, path := range []string{"/update-info.json", "/update-info"} {
		routePath := path
		r.GET(routePath, func(c *gin.Context) {
			payload := map[string]interface{}{}
			if utils.FileExists(updateInfoPath) {
				if data, err := utils.ReadJSONFile(updateInfoPath); err == nil {
					payload = data
				}
			}

			products := utils.GetProductsInfo(downloadsDir, logger)
			productName, latest := utils.GetLatestProductRelease(products, "YMhut Box")
			if latest != nil {
				baseURL := requestBaseURL(c)
				payload["app_version"] = latest.Version
				payload["download_url"] = baseURL + latest.DownloadPath
				payload["download_mirrors"] = []map[string]interface{}{
					{
						"id":      "primary",
						"name":    "官方直连",
						"url":     baseURL + latest.DownloadPath,
						"type":    "direct",
						"sha256":  sha256File(filepath.Join(downloadsDir, latest.FileName)),
						"enabled": true,
					},
				}
				payload["detected_product"] = productName
				payload["detected_packages"] = products
			}

			c.Header("Content-Type", "application/json; charset=utf-8")
			c.Header("Cache-Control", "public, max-age=300")
			c.JSON(http.StatusOK, payload)
		})
		logger.Info(fmt.Sprintf("注册动态更新信息路由 [GET] %s", routePath))
	}
}

func requestBaseURL(c *gin.Context) string {
	scheme := c.GetHeader("X-Forwarded-Proto")
	if scheme == "" {
		if c.Request.TLS != nil {
			scheme = "https"
		} else {
			scheme = "http"
		}
	}
	return scheme + "://" + c.Request.Host
}

func registerReleaseAPIRoutes(
	r *gin.Engine,
	logger *utils.Logger,
	publicDir string,
	downloadsDir string,
) {
	r.GET("/api/releases", func(c *gin.Context) {
		c.JSON(http.StatusOK, buildReleaseManifest(c, logger, publicDir, downloadsDir))
	})
	r.GET("/api/modules", func(c *gin.Context) {
		manifest := buildReleaseManifest(c, logger, publicDir, downloadsDir)
		c.JSON(http.StatusOK, gin.H{
			"manifest_version": manifest["manifest_version"],
			"modules":          manifest["modules"],
		})
	})
	r.GET("/api/update-info", func(c *gin.Context) {
		c.JSON(http.StatusOK, buildReleaseManifest(c, logger, publicDir, downloadsDir))
	})
}

func buildReleaseManifest(
	c *gin.Context,
	logger *utils.Logger,
	publicDir string,
	downloadsDir string,
) map[string]interface{} {
	payload := map[string]interface{}{}
	updateInfoPath := filepath.Join(publicDir, "update-info.json")
	if utils.FileExists(updateInfoPath) {
		if data, err := utils.ReadJSONFile(updateInfoPath); err == nil {
			payload = data
		}
	}

	baseURL := requestBaseURL(c)
	products := utils.GetProductsInfo(downloadsDir, logger)
	packages := make([]map[string]interface{}, 0)
	for productName, releases := range products {
		for _, release := range releases {
			filePath := filepath.Join(downloadsDir, release.FileName)
			platform, arch := detectPackagePlatform(release.FileName, release.Extension)
			packages = append(packages, map[string]interface{}{
				"id":        packageID(productName, platform, arch, release.Version),
				"name":      productName,
				"version":   release.Version,
				"platform":  platform,
				"arch":      arch,
				"url":       baseURL + release.DownloadPath,
				"sha256":    sha256File(filePath),
				"size":      release.SizeBytes,
				"required":  utils.IsSameProduct(productName, "YMhut Box"),
				"enabled":   true,
				"changelog": map[string]string{},
			})
		}
	}

	modulesPath := filepath.Join(publicDir, "modules.json")
	modules := []interface{}{}
	if utils.FileExists(modulesPath) {
		if data, err := utils.ReadJSONFile(modulesPath); err == nil {
			if raw, ok := data["modules"].([]interface{}); ok {
				modules = raw
			}
		}
	}

	payload["manifest_version"] = 2
	payload["packages"] = packages
	payload["modules"] = modules
	payload["assets"] = []interface{}{}

	productName, latest := utils.GetLatestProductRelease(products, "YMhut Box")
	if latest != nil {
		latestURL := baseURL + latest.DownloadPath
		payload["app_version"] = latest.Version
		payload["download_url"] = latestURL
		payload["download_mirrors"] = []map[string]interface{}{
			{
				"id":      "primary",
				"name":    "官方下载",
				"url":     latestURL,
				"type":    "direct",
				"sha256":  sha256File(filepath.Join(downloadsDir, latest.FileName)),
				"enabled": true,
			},
		}
		payload["detected_product"] = productName
		payload["detected_packages"] = products
	}

	return payload
}

func packageID(productName string, platform string, arch string, version string) string {
	value := strings.ToLower(productName + "-" + platform + "-" + arch + "-" + version)
	value = strings.NewReplacer(" ", "-", "_", "-").Replace(value)
	return value
}

func detectPackagePlatform(fileName string, ext string) (string, string) {
	lower := strings.ToLower(fileName)
	platform := "unknown"
	switch strings.ToLower(ext) {
	case "exe", "msi":
		platform = "windows"
	case "apk":
		platform = "android"
	case "dmg", "pkg":
		platform = "macos"
	case "deb", "rpm", "appimage", "tar.gz":
		platform = "linux"
	}

	arch := "x64"
	if strings.Contains(lower, "arm64") || strings.Contains(lower, "aarch64") {
		arch = "arm64"
	} else if strings.Contains(lower, "x86") && !strings.Contains(lower, "x64") {
		arch = "x86"
	} else if platform == "android" {
		arch = "universal"
	}
	return platform, arch
}

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

	hash := sha256.New()
	if _, err := io.Copy(hash, file); err != nil {
		return ""
	}
	return hex.EncodeToString(hash.Sum(nil))
}
