package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

// --- 数据库模型 ---

type TemplateConfig struct {
	ID         uint      `gorm:"primaryKey" json:"id"`
	ConfigData string    `gorm:"type:json;column:config_data" json:"config_data"`
	UpdatedAt  time.Time `json:"updated_at"`
}

type Rsvp struct {
	ID         uint      `gorm:"primaryKey" json:"id"`
	Name       string    `gorm:"column:name;size:50" json:"name"`
	GuestCount string    `gorm:"column:guest_count;size:20" json:"guest_count"` // 必须与前端 guest_count 匹配
	Wishes     string    `gorm:"column:wishes;type:text" json:"wishes"`
	CreatedAt  time.Time `gorm:"column:created_at" json:"created_at"`
}

var db *gorm.DB

// 后台管理员凭据与签名密钥（可用环境变量覆盖）
var (
	adminAccount  = getEnv("ADMIN_ACCOUNT", "admin")
	adminPassword = getEnv("ADMIN_PASSWORD", "admin123")
	adminSecret   = getEnv("ADMIN_SECRET", "wedding-admin-secret-2026")
)

func getEnv(k, def string) string {
	if v := os.Getenv(k); v != "" {
		return v
	}
	return def
}

// 签发后台令牌：base64(payload).base64(hmac(payload, secret))
func signToken(account string) string {
	payload := fmt.Sprintf(`{"account":%q,"exp":%d}`, account, time.Now().Add(24*time.Hour).Unix())
	b64 := base64.StdEncoding.EncodeToString([]byte(payload))
	mac := hmac.New(sha256.New, []byte(adminSecret))
	mac.Write([]byte(b64))
	sig := base64.StdEncoding.EncodeToString(mac.Sum(nil))
	return b64 + "." + sig
}

// 校验后台令牌
func verifyToken(token string) bool {
	parts := strings.Split(token, ".")
	if len(parts) != 2 {
		return false
	}
	mac := hmac.New(sha256.New, []byte(adminSecret))
	mac.Write([]byte(parts[0]))
	expected := base64.StdEncoding.EncodeToString(mac.Sum(nil))
	if !hmac.Equal([]byte(expected), []byte(parts[1])) {
		return false
	}
	raw, err := base64.StdEncoding.DecodeString(parts[0])
	if err != nil {
		return false
	}
	var claims struct {
		Account string `json:"account"`
		Exp     int64  `json:"exp"`
	}
	if err := json.Unmarshal(raw, &claims); err != nil {
		return false
	}
	return time.Now().Unix() <= claims.Exp
}

func bearerToken(c *gin.Context) string {
	return strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
}

func initDB() {
	// DSN: 用户名:密码@tcp(地址:端口)/数据库名
	dsn := "root:root@tcp(127.0.0.1:3306)/nl_wedding?charset=utf8mb4&parseTime=True&loc=Local"
	var err error
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatalf("数据库连接失败: %v", err)
	}

	// 自动同步表结构
	_ = db.AutoMigrate(&TemplateConfig{}, &Rsvp{})

	// 确保上传目录存在
	_ = os.MkdirAll("./uploads", os.ModePerm)

	fmt.Println("数据库初始化成功，表结构已同步，上传目录已就绪")
}

func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}
		c.Next()
	}
}

func main() {
	initDB()
	r := gin.Default()
	r.Use(corsMiddleware())

	// 静态文件目录，用于预览上传的图片
	r.Static("/uploads", "./uploads")

	api := r.Group("/api")
	{
		// 0. 后台登录
		api.POST("/admin/login", func(c *gin.Context) {
			var body struct {
				Account  string `json:"account"`
				Password string `json:"password"`
			}
			if err := c.ShouldBindJSON(&body); err != nil {
				c.JSON(400, gin.H{"error": "参数错误"})
				return
			}
			if body.Account != adminAccount || body.Password != adminPassword {
				c.JSON(401, gin.H{"error": "账号或密码错误"})
				return
			}
			c.JSON(200, gin.H{
				"code":  200,
				"token": signToken(body.Account),
				"user":  gin.H{"account": body.Account},
			})
		})

		// 1. 获取配置
		api.GET("/config", func(c *gin.Context) {
			var config TemplateConfig
			if err := db.First(&config, 1).Error; err != nil {
				c.JSON(200, gin.H{"code": 200, "data": "{}"})
				return
			}
			c.JSON(200, gin.H{"code": 200, "data": config.ConfigData})
		})

		// 2. 保存配置（需后台鉴权）
		api.POST("/config", func(c *gin.Context) {
			if !verifyToken(bearerToken(c)) {
				c.JSON(401, gin.H{"error": "未授权，请先登录后台"})
				return
			}
			var rawBody interface{}
			if err := c.BindJSON(&rawBody); err != nil {
				c.JSON(400, gin.H{"error": "无效的JSON数据"})
				return
			}
			jsonBytes, _ := json.Marshal(rawBody)

			var config TemplateConfig
			db.FirstOrCreate(&config, 1)
			config.ConfigData = string(jsonBytes)

			if err := db.Save(&config).Error; err != nil {
				c.JSON(500, gin.H{"error": "数据库保存配置失败"})
				return
			}
			c.JSON(200, gin.H{"code": 200, "msg": "配置保存成功"})
		})

		// 3. 提交出席信息
		api.POST("/rsvp", func(c *gin.Context) {
			var rsvp Rsvp
			// BindJSON 会根据 json 标签映射字段
			if err := c.ShouldBindJSON(&rsvp); err != nil {
				c.JSON(400, gin.H{"error": "表单数据绑定失败", "details": err.Error()})
				return
			}

			if err := db.Create(&rsvp).Error; err != nil {
				c.JSON(500, gin.H{"error": "出席信息写入数据库失败"})
				return
			}
			c.JSON(200, gin.H{"code": 200, "msg": "回执提交成功"})
		})

		// 4. 图片上传接口 (补全)
		api.POST("/upload", func(c *gin.Context) {
			file, err := c.FormFile("file")
			if err != nil {
				c.JSON(400, gin.H{"error": "未获取到上传文件"})
				return
			}

			// 生成唯一文件名
			filename := fmt.Sprintf("%d_%s", time.Now().Unix(), filepath.Base(file.Filename))
			savePath := filepath.Join("uploads", filename)

			if err := c.SaveUploadedFile(file, savePath); err != nil {
				c.JSON(500, gin.H{"error": "文件保存至服务器失败"})
				return
			}

			// 返回前端可访问的完整 URL
			fileUrl := fmt.Sprintf("http://localhost:8080/uploads/%s", filename)
			c.JSON(200, gin.H{
				"code": 200,
				"url":  fileUrl,
			})
		})

		// 5. 出席回执列表（需后台鉴权）
		api.GET("/rsvp/list", func(c *gin.Context) {
			if !verifyToken(bearerToken(c)) {
				c.JSON(401, gin.H{"error": "未授权，请先登录后台"})
				return
			}
			var list []Rsvp
			db.Order("created_at desc").Find(&list)
			c.JSON(200, gin.H{"code": 200, "data": list})
		})
	}

	fmt.Println("婚礼纪后端 API 已在 http://localhost:8080 启动")
	r.Run(":8080")
}
