package middleware

import (
	"errors"
	"net/http"
	"strconv"
	"strings"

	"kra/internal/biz"
	"kra/internal/service"

	"github.com/gin-gonic/gin"
	"golang.org/x/sync/singleflight"
)

const claimsKey = "admin_claims"

var refreshTokens singleflight.Group

func Auth(auth *service.AuthService) gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.GetHeader("x-token")
		if token == "" {
			token, _ = c.Cookie("x-token")
		}
		if token == "" {
			NoAuth(c, "未登录或非法访问，请登录")
			return
		}
		value, err, _ := refreshTokens.Do(token, func() (any, error) {
			return auth.AuthenticateToken(c.Request.Context(), token)
		})
		if err != nil {
			message := "无法处理此token"
			switch {
			case errors.Is(err, biz.ErrTokenExpired):
				message = "登录已过期，请重新登录"
			case errors.Is(err, biz.ErrTokenMalformed):
				message = "这不是一个token"
			case errors.Is(err, biz.ErrTokenSignatureInvalid):
				message = "无效签名"
			case errors.Is(err, biz.ErrTokenNotValidYet):
				message = "token尚未激活"
			case errors.Is(err, biz.ErrTokenDisabled):
				message = "您的帐户异地登陆或令牌失效"
			}
			SetTokenCookie(c, "", -1)
			NoAuth(c, message)
			return
		}
		authentication := value.(*biz.TokenAuthentication)
		if authentication.Refreshed != nil {
			c.Header("new-token", authentication.Refreshed.Value)
			c.Header("new-expires-at", strconv.FormatInt(authentication.Refreshed.ExpiresAt.Unix(), 10))
			SetTokenCookie(c, authentication.Refreshed.Value, int(authentication.Refreshed.TTL.Seconds()))
		}
		c.Set(claimsKey, authentication.Claims)
		c.Next()
	}
}

func Claims(c *gin.Context) *biz.AuthClaims {
	value, _ := c.Get(claimsKey)
	claims, _ := value.(*biz.AuthClaims)
	return claims
}

func MustChangePassword() gin.HandlerFunc {
	return func(c *gin.Context) {
		claims := Claims(c)
		if claims == nil || !claims.MustChangePwd {
			c.Next()
			return
		}
		path := strings.TrimSuffix(c.Request.URL.Path, "/")
		if strings.HasSuffix(path, "/user/changePassword") || strings.HasSuffix(path, "/user/getUserInfo") || strings.HasSuffix(path, "/jwt/jsonInBlacklist") {
			c.Next()
			return
		}
		c.AbortWithStatusJSON(http.StatusConflict, Response{Code: CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "密码已过期，请先修改密码"})
	}
}
