package main

import (
	"compress/gzip"
	"embed"
	"fmt"
	"io/fs"
	"log"
	"net/url"
	"os"
	"strings"
	"verification-platform-backend/internal/config"
	"verification-platform-backend/internal/database"
	"verification-platform-backend/internal/middleware"
	"verification-platform-backend/internal/router"
	"verification-platform-backend/internal/scheduler"
	"verification-platform-backend/internal/service"
	"verification-platform-backend/pkg/logger"

	"github.com/gin-gonic/gin"
	"golang.org/x/crypto/bcrypt"
)

//go:embed all:embedded/dist
var embeddedFiles embed.FS

func main() {
	if len(os.Args) > 1 {
		switch os.Args[1] {
		case "list-admin":
			listAdmins()
		case "reset-password":
			resetPassword()
		case "create-admin":
			createAdmin()
		case "help", "-h", "--help":
			printHelp()
		default:
			fmt.Printf("未知命令: %s\n", os.Args[1])
			printHelp()
			os.Exit(1)
		}
		return
	}

	startServer()
}

func printHelp() {
	fmt.Println("用法:")
	fmt.Println("  verify                    启动服务器")
	fmt.Println("  verify list-admin         列出所有管理员账号")
	fmt.Println("  verify reset-password <用户名> [新密码]  重置管理员密码")
	fmt.Println("  verify create-admin <用户名> <密码> [邮箱]  创建新管理员")
	fmt.Println("  verify help               显示帮助信息")
	fmt.Println()
	fmt.Println("示例:")
	fmt.Println("  verify list-admin")
	fmt.Println("  verify reset-password admin")
	fmt.Println("  verify reset-password admin newpass")
	fmt.Println("  verify create-admin newadmin password123 admin@example.com")
}

func listAdmins() {
	config.Init()
	database.Init()

	var users []struct {
		ID        uint
		Username  string
		Email     *string
		Role      string
		Status    string
		CreatedAt string
	}

	if err := database.DB.Table("users").Where("role = ?", "admin").Find(&users).Error; err != nil {
		fmt.Printf("错误: 查询失败 - %v\n", err)
		os.Exit(1)
	}

	if len(users) == 0 {
		fmt.Println("没有找到管理员账号")
		return
	}

	fmt.Println("管理员账号列表:")
	fmt.Println("----------------------------------------")
	for _, u := range users {
		email := ""
		if u.Email != nil {
			email = *u.Email
		}
		fmt.Printf("ID: %d  用户名: %s  邮箱: %s  状态: %s\n", u.ID, u.Username, email, u.Status)
	}
	fmt.Println("----------------------------------------")
}

func createAdmin() {
	config.Init()
	database.Init()

	if len(os.Args) < 4 {
		fmt.Println("用法: verify create-admin <用户名> <密码> [邮箱]")
		os.Exit(1)
	}

	username := os.Args[2]
	password := os.Args[3]
	email := ""
	if len(os.Args) >= 5 {
		email = os.Args[4]
	}

	if len(password) < 6 {
		fmt.Println("错误: 密码长度至少6位")
		os.Exit(1)
	}

	var count int64
	database.DB.Table("users").Where("username = ?", username).Count(&count)
	if count > 0 {
		fmt.Printf("错误: 用户名 '%s' 已存在\n", username)
		os.Exit(1)
	}

	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		fmt.Printf("错误: 密码加密失败 - %v\n", err)
		os.Exit(1)
	}

	emailPtr := (*string)(nil)
	if email != "" {
		emailPtr = &email
	}

	if err := database.DB.Table("users").Create(map[string]interface{}{
		"username": username,
		"password": string(hashedPassword),
		"email":    emailPtr,
		"role":     "admin",
		"status":   "active",
	}).Error; err != nil {
		fmt.Printf("错误: 创建失败 - %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("成功: 管理员账号 '%s' 已创建\n", username)
}

func resetPassword() {
	config.Init()
	database.Init()

	if len(os.Args) < 3 {
		fmt.Println("用法: verify reset-password <用户名> [新密码]")
		os.Exit(1)
	}

	username := os.Args[2]
	newPassword := ""
	if len(os.Args) >= 4 {
		newPassword = os.Args[3]
	}

	var user struct {
		ID       uint
		Username string
		Role     string
	}

	if err := database.DB.Table("users").Where("username = ? AND role = ?", username, "admin").First(&user).Error; err != nil {
		fmt.Printf("错误: 未找到管理员用户 '%s'\n", username)
		os.Exit(1)
	}

	if newPassword == "" {
		newPassword = generateRandomPassword(12)
	}

	if len(newPassword) < 6 {
		fmt.Println("错误: 密码长度至少6位")
		os.Exit(1)
	}

	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
	if err != nil {
		fmt.Printf("错误: 密码加密失败 - %v\n", err)
		os.Exit(1)
	}

	if err := database.DB.Table("users").Where("id = ?", user.ID).Update("password", string(hashedPassword)).Error; err != nil {
		fmt.Printf("错误: 更新密码失败 - %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("成功: 用户 '%s' 的密码已重置\n", username)
	fmt.Printf("新密码: %s\n", newPassword)
}

func generateRandomPassword(length int) string {
	const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%"
	result := make([]byte, length)
	for i := range result {
		result[i] = chars[i%len(chars)]
	}
	for i := len(result) - 1; i > 0; i-- {
		j := i % len(chars)
		result[i], result[j] = result[j], result[i]
	}
	return string(result)
}

func startServer() {
	config.Init()
	logger.Init()
	database.Init()

	os.MkdirAll("uploads", 0755)

	if config.GetString("app.env") == "production" {
		gin.SetMode(gin.ReleaseMode)
	}

	r := gin.Default()
	r.MaxMultipartMemory = 32 << 20

	r.Use(middleware.Logger())
	r.Use(middleware.Recovery())
	r.Use(middleware.Cors())
	r.Use(gzipMiddleware())

	r.Use(func(c *gin.Context) {
		if strings.HasPrefix(c.Request.URL.Path, "/uploads/") {
			c.Header("Cache-Control", "public, max-age=31536000, immutable")
		}
		c.Next()
	})

	r.Static("/uploads", "uploads")
	router.SetupRoutes(r)
	setupEmbeddedFrontend(r)

	scheduler.StartCleanupScheduler()

	port := config.GetString("app.port")
	if port == "" {
		port = "8080"
	}

	log.Printf("Server starting on port %s", port)
	if err := r.Run(":" + port); err != nil {
		log.Fatal("Failed to start server: ", err)
	}
}

func setupEmbeddedFrontend(r *gin.Engine) {
	distFS, err := fs.Sub(embeddedFiles, "embedded/dist")
	if err != nil {
		log.Printf("Warning: embedded dist not found, frontend will not be served")
		return
	}

	indexHTML, err := fs.ReadFile(distFS, "index.html")
	if err != nil {
		log.Printf("Warning: index.html not found in embedded dist")
		return
	}

	r.GET("/assets/*filepath", func(c *gin.Context) {
		filepath := c.Param("filepath")
		filepath = strings.TrimPrefix(filepath, "/")
		decodedPath, err := url.PathUnescape(filepath)
		if err != nil {
			decodedPath = filepath
		}

		fullPath := "assets/" + decodedPath

		data, err := fs.ReadFile(distFS, fullPath)
		if err != nil {
			c.Status(404)
			return
		}

		contentType := getContentTypeByExtension(decodedPath)

		c.Header("Cache-Control", "public, max-age=31536000, immutable")
		c.Data(200, contentType, data)
	})

	r.NoRoute(func(c *gin.Context) {
		if c.Request.Method != "GET" || strings.HasPrefix(c.Request.URL.Path, "/api/") {
			c.JSON(404, gin.H{"error": "not found"})
			return
		}

		path := strings.TrimPrefix(c.Request.URL.Path, "/")
		decodedPath, err := url.PathUnescape(path)
		if err != nil {
			decodedPath = path
		}
		if data, err := fs.ReadFile(distFS, decodedPath); err == nil {
			contentType := getContentTypeByExtension(decodedPath)
			c.Data(200, contentType, data)
			return
		}

		c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
		serveIndexHTML(c, string(indexHTML))
	})
}

func serveIndexHTML(c *gin.Context, indexHTML string) {
	settingService := service.NewSettingService()
	settings, err := settingService.GetSettingsByCategory("basic")
	if err != nil {
		settings = make(map[string]interface{})
	}

	siteName := getSettingString(settings, "siteName", "微授权")
	siteDescription := getSettingString(settings, "siteDescription", "专业的应用验证平台")
	siteKeywords := getSettingString(settings, "siteKeywords", "验证平台,软件授权,卡密验证")
	logoLight := getSettingString(settings, "logoLight", "")
	logoDark := getSettingString(settings, "logoDark", "")
	favicon := getSettingString(settings, "favicon", "")

	html := indexHTML
	html = strings.ReplaceAll(html, "{{.SiteName}}", siteName)
	html = strings.ReplaceAll(html, "{{.SiteDescription}}", siteDescription)
	html = strings.ReplaceAll(html, "{{.SiteKeywords}}", siteKeywords)
	html = strings.ReplaceAll(html, "{{.LogoLight}}", logoLight)
	html = strings.ReplaceAll(html, "{{.LogoDark}}", logoDark)
	html = strings.ReplaceAll(html, "{{.Favicon}}", favicon)

	c.Data(200, "text/html; charset=utf-8", []byte(html))
}

var fallbackMimeTypes = map[string]string{
	".js":   "application/javascript; charset=utf-8",
	".mjs":  "application/javascript; charset=utf-8",
	".css":  "text/css; charset=utf-8",
	".html": "text/html; charset=utf-8",
	".json": "application/json; charset=utf-8",
	".svg":  "image/svg+xml",
	".png":  "image/png",
	".jpg":  "image/jpeg",
	".jpeg": "image/jpeg",
	".gif":  "image/gif",
	".ico":  "image/x-icon",
	".webp": "image/webp",
	".woff": "font/woff",
	".woff2": "font/woff2",
	".ttf":  "font/ttf",
	".eot":  "application/vnd.ms-fontobject",
	".xml":  "application/xml",
	".txt":  "text/plain; charset=utf-8",
}

func getContentTypeByExtension(filename string) string {
	ext := strings.ToLower(filename[strings.LastIndex(filename, "."):])
	if ct, ok := fallbackMimeTypes[ext]; ok {
		return ct
	}
	return "application/octet-stream"
}

func getSettingString(settings map[string]interface{}, key, defaultValue string) string {
	if val, ok := settings[key]; ok {
		if str, ok := val.(string); ok && str != "" {
			return str
		}
	}
	return defaultValue
}

func isCompressibleContentType(contentType string) bool {
	return strings.HasPrefix(contentType, "text/") ||
		strings.Contains(contentType, "javascript") ||
		strings.Contains(contentType, "json") ||
		strings.Contains(contentType, "xml") ||
		strings.Contains(contentType, "svg")
}

func gzipMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		if !strings.Contains(c.GetHeader("Accept-Encoding"), "gzip") {
			c.Next()
			return
		}

		c.Header("Vary", "Accept-Encoding")

		gw := &gzipResponseWriter{ResponseWriter: c.Writer, writer: gzip.NewWriter(c.Writer)}
		c.Writer = gw
		c.Next()

		if gw.compressed {
			gw.writer.Close()
		}
	}
}

type gzipResponseWriter struct {
	gin.ResponseWriter
	writer     *gzip.Writer
	compressed bool
}

func (w *gzipResponseWriter) Write(data []byte) (int, error) {
	if !w.compressed {
		contentType := w.Header().Get("Content-Type")
		if isCompressibleContentType(contentType) {
			w.Header().Del("Content-Length")
			w.Header().Set("Content-Encoding", "gzip")
			w.compressed = true
		}
	}

	if w.compressed {
		return w.writer.Write(data)
	}
	return w.ResponseWriter.Write(data)
}

func (w *gzipResponseWriter) WriteString(s string) (int, error) {
	if !w.compressed {
		contentType := w.Header().Get("Content-Type")
		if isCompressibleContentType(contentType) {
			w.Header().Del("Content-Length")
			w.Header().Set("Content-Encoding", "gzip")
			w.compressed = true
		}
	}

	if w.compressed {
		return w.writer.Write([]byte(s))
	}
	return w.ResponseWriter.WriteString(s)
}
