package middleware

import (
	"errors"
	"strconv"
	"time"

	"kra/pkg/jwt"
	"kra/pkg/response"
	"kra/pkg/utils"

	jwtv5 "github.com/golang-jwt/jwt/v5"

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

// BlacklistChecker JWT黑名单检查接口
type BlacklistChecker interface {
	IsBlacklist(jwt string) bool
}

// JWTConfig JWT中间件配置
type JWTConfig struct {
	JWT              *jwt.JWT
	BlacklistChecker BlacklistChecker
	UseMultipoint    bool
	SetRedisJWT      func(token, username string) error
}

// 全局配置（需要在初始化时设置）
var jwtConfig *JWTConfig

// SetJWTConfig 设置JWT配置
func SetJWTConfig(cfg *JWTConfig) {
	jwtConfig = cfg
	// 同时设置 utils 包的 JWT 实例
	if cfg != nil && cfg.JWT != nil {
		utils.SetJWT(cfg.JWT)
	}
}

// JWTAuth JWT认证中间件（与 kra 保持一致）
func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
		token := utils.GetToken(c)
		if token == "" {
			response.NoAuth("未登录或非法访问，请登录", c)
			c.Abort()
			return
		}

		// 检查黑名单
		if jwtConfig != nil && jwtConfig.BlacklistChecker != nil && jwtConfig.BlacklistChecker.IsBlacklist(token) {
			response.NoAuth("您的帐户异地登陆或令牌失效", c)
			utils.ClearToken(c)
			c.Abort()
			return
		}

		if jwtConfig == nil || jwtConfig.JWT == nil {
			response.NoAuth("JWT配置错误", c)
			c.Abort()
			return
		}

		// parseToken 解析token包含的信息
		claims, err := jwtConfig.JWT.ParseToken(token)
		if err != nil {
			if errors.Is(err, jwt.ErrTokenExpired) {
				response.NoAuth("登录已过期，请重新登录", c)
				utils.ClearToken(c)
				c.Abort()
				return
			}
			response.NoAuth(err.Error(), c)
			utils.ClearToken(c)
			c.Abort()
			return
		}

		// 已登录用户被管理员禁用 需要使该用户的jwt失效 此处比较消耗性能 如果需要 请自行打开
		// 用户被删除的逻辑 需要优化 此处比较消耗性能 如果需要 请自行打开

		c.Set("claims", claims)

		// 检查是否需要刷新token
		if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime {
			claims.ExpiresAt = jwtv5.NewNumericDate(time.Now().Add(jwtConfig.JWT.ExpiresAt))
			newToken, newClaims, err := jwtConfig.JWT.CreateTokenByOldToken(token, *claims)
			if err == nil {
				c.Header("new-token", newToken)
				c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10))
				utils.SetToken(c, newToken, int(jwtConfig.JWT.ExpiresAt.Seconds()/60))

				// 多点登录记录新JWT
				if jwtConfig.UseMultipoint && jwtConfig.SetRedisJWT != nil {
					_ = jwtConfig.SetRedisJWT(newToken, newClaims.Username)
				}
			}
		}

		c.Next()

		// 处理后续中间件设置的新token
		if newToken, exists := c.Get("new-token"); exists {
			c.Header("new-token", newToken.(string))
		}
		if newExpiresAt, exists := c.Get("new-expires-at"); exists {
			c.Header("new-expires-at", newExpiresAt.(string))
		}
	}
}
