package jwt

import (
	"context"
	"errors"
	"github.com/gogf/gf/v2/frame/g"
	"server/utility/ecode"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
)

var (
	secretKey = []byte("1a40c1d5b7a1b0f0fasdfaf835e0b24ca292a6")
	issuer    = "novel"
)

type (
	TokenIn struct {
		UserId int64  // 用户 ID
		Role   string // 权限标识
	}
	TokenOut struct {
		UserId int64  // 用户 ID
		Role   string // 权限标识
		JTI    string // JWT 唯一标识
	}

	jwtClaims struct {
		UserId int64  `json:"user_id"` // 用户 ID
		Role   string `json:"Role"`    // 权限标识
		JTI    string `json:"jti"`     // 唯一标识
		jwt.RegisteredClaims
	}
)

func GenerateToken(in *TokenIn) (string, error) {
	claims := jwtClaims{
		UserId: in.UserId,
		Role:   in.Role,
		JTI:    uuid.NewString(),
		RegisteredClaims: jwt.RegisteredClaims{
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			NotBefore: jwt.NewNumericDate(time.Now()),
			Issuer:    issuer,
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(secretKey)
}

func ParseToken(tokenString string) (*TokenOut, error) {
	if strings.HasPrefix(tokenString, "Bearer ") {
		tokenString = strings.TrimPrefix(tokenString, "Bearer ")
	}

	token, err := jwt.ParseWithClaims(tokenString, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) {
		return secretKey, nil
	})
	if err != nil {
		if errors.Is(err, jwt.ErrTokenExpired) {
			return nil, ecode.Expire.Sub("token_expired")
		}
		return nil, ecode.Fail.Sub("token_parse_failed")
	}

	claims, ok := token.Claims.(*jwtClaims)
	if !ok || !token.Valid {
		return nil, ecode.InvalidOperation.Sub("invalid_token")
	}

	blacklist, err := isBlacklist(claims.JTI)
	if err != nil {
		return nil, ecode.Fail.Sub("token_parse_failed")
	}
	if blacklist {
		return nil, ecode.InvalidOperation.Sub("token_expired")
	}
	return &TokenOut{
		UserId: claims.UserId,
		Role:   claims.Role,
		JTI:    claims.JTI,
	}, nil
}
func isBlacklist(uuid string) (bool, error) {
	exitst, err := g.Redis().Exists(context.Background(), "blacklist:"+uuid)
	if err != nil {
		return false, err
	}
	return exitst > 0, nil
}
