// Package middleware 安全校验中间件
package middleware

import (
	"crypto/md5"
	"encoding/hex"
	"strconv"
	"time"

	"server/common"
	"server/config"

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

// SecurityMiddleware 安全校验中间件
// 防止暴力入侵，校验请求头签名
// 请求头需携带:
//   - X-App-Sign: 签名值 = MD5(timestamp + secretKey)
//   - X-App-Timestamp: 时间戳(毫秒)
func SecurityMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		cfg := config.AppConfig.Security

		// 未启用则跳过
		if !cfg.Enable {
			c.Next()
			return
		}

		// 白名单路径跳过
		path := c.Request.URL.Path
		if isSecurityWhitelist(path) {
			c.Next()
			return
		}

		// 获取签名和时间戳
		sign := c.GetHeader(cfg.HeaderKey)
		timestamp := c.GetHeader("X-App-Timestamp")

		if sign == "" || timestamp == "" {
			common.Warn("安全校验失败: 缺少签名头 IP=%s Path=%s", c.ClientIP(), path)
			common.Error(c, 403, "非法请求")
			c.Abort()
			return
		}

		// 验证时间戳 (5分钟内有效)
		ts, err := strconv.ParseInt(timestamp, 10, 64)
		if err != nil {
			common.Warn("安全校验失败: 时间戳格式错误 IP=%s", c.ClientIP())
			common.Error(c, 403, "非法请求")
			c.Abort()
			return
		}

		now := time.Now().UnixMilli()
		if abs(now-ts) > 5*60*1000 { // 5分钟
			common.Warn("安全校验失败: 时间戳过期 IP=%s Timestamp=%d", c.ClientIP(), ts)
			common.Error(c, 403, "请求已过期")
			c.Abort()
			return
		}

		// 验证签名
		expectedSign := generateSign(timestamp, cfg.SecretKey)
		if sign != expectedSign {
			common.Warn("安全校验失败: 签名错误 IP=%s Sign=%s Expected=%s", c.ClientIP(), sign, expectedSign)
			common.Error(c, 403, "签名错误")
			c.Abort()
			return
		}

		c.Next()
	}
}

// generateSign 生成签名
func generateSign(timestamp, secretKey string) string {
	data := timestamp + secretKey
	hash := md5.Sum([]byte(data))
	return hex.EncodeToString(hash[:])
}

// 安全校验白名单
var securityWhitelist = []string{
	"/swagger/",
	"/swagger/index.html",
}

func isSecurityWhitelist(path string) bool {
	for _, white := range securityWhitelist {
		if len(path) >= len(white) && path[:len(white)] == white {
			return true
		}
	}
	return false
}

func abs(n int64) int64 {
	if n < 0 {
		return -n
	}
	return n
}

// AddSecurityWhitelist 添加安全校验白名单
func AddSecurityWhitelist(paths ...string) {
	securityWhitelist = append(securityWhitelist, paths...)
}
