package xpink_auth

import (
	"ai-css/library/logger"
	"crypto/rsa"
	"fmt"
	"net/url"
	"strings"
	"sync"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
)

var (
	IdentityKey      = "XPINK_USER"
	TokenLookup      = "header:Authorization,query:Authorization,referer:Authorization"
	SigningAlgorithm = "RS256"
	pubKey           *rsa.PublicKey // runtime load

	pubkeyContent = []byte(`-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqD/o6TI7AZyNEbFQTy4g
K4Hd+aLAoLRwOe0iKqDWK4HRZABtLLvLFZLdwP4iUNAQOoy+WXz3CGqwzvs36531
6rOzeCKtYGSN64+Pnn6UWaricnCZ2Tqng2eNln9kHALbguGVtrOSQNZr97OCOOk3
ZDCnNwnz0hA9AhIRX1LNswPPC18q2Itdb5C//nxoEJPyY3u0r1YDL6sPD1eUDI0x
+4A8Dgqny4Z84XALn2ucR9bcUGSbtyTR1pg42MYyw6I7MV4P0YGXD3kcItd+9qlX
rULFZh5RLFl52PeA7bmXUpxKeg2lvv4CzNlk+eM7UyHctjYmM5rk+6QencjHk+qo
doVMzeX0e3sby72aq7g66QWThwGgVwwRFxsodtSwl6TAXH3TAVd3nyZ9tSqM/BT7
B8acMVzG/lzMVvrEtJHUcPlfHNDKmWuLWo6ywblc/MGj7z8Fe/pk+wJ1Nv4WCBMj
3kv4durqVNh4YhPvxt+wAZzsNxmliFEGXb+yC/8qpZv13EgNt4f1voKYML7StIj5
oYslqoYvzN3j5ROBRDlJaxqErEwDLwEeiqBuSME6H6hJFD3SRujmcdFtl4GYyZb9
F7VlEGHjQqKljkjB5DOno2tV5EzGNu21dAwBHSHfto7nqG781QmQrDAVs681pNpU
iWNoAGc0L/VR0YPuV2X+ml8CAwEAAQ==
-----END PUBLIC KEY-----`)

	key           = ""
	TokenHeadName = "Bearer"

	ErrEmptyFormToken   = fmt.Errorf("empty form token")
	ErrEmptyParamToken  = fmt.Errorf("empty param token")
	ErrEmptyCookieToken = fmt.Errorf("empty cookie token")
	ErrEmptyQueryToken  = fmt.Errorf("empty query token")
	ErrEmptyAuthHeader  = fmt.Errorf("empty auth header")

	ErrInvalidAuthHeader = fmt.Errorf("invalid auth header")

	loadPbk sync.Once
)

type UserSession struct {
	Id       uint64
	Userno   string
	NickName string
	Jti      string
}

func Identity(c *gin.Context) interface{} {

	loadPbk.Do(func() {
		var err error
		pubKey, err = jwt.ParseRSAPublicKeyFromPEM(pubkeyContent)
		if err != nil {
			logger.Error("parse rsa public key fail err:%v", err)
		}
	})

	claims, err := GetClaimsFromJWT(c)
	if err != nil {
		logger.Error("parse claims failed", err)
	}

	c.Set("JWT_PAYLOAD", claims)
	return JwtToUserSession(ExtractClaims(c))
}

func JwtToUserSession(payload jwt.MapClaims) UserSession {
	// 安全地提取 Id
	var id uint64
	if idVal, ok := payload["Id"]; ok && idVal != nil {
		switch v := idVal.(type) {
		case float64:
			id = uint64(v)
		case int64:
			id = uint64(v)
		case int:
			id = uint64(v)
		case uint64:
			id = v
		}
	}

	// 安全地提取其他字段
	userno := ""
	if val, ok := payload["Userno"]; ok && val != nil {
		userno = val.(string)
	}

	nickName := ""
	if val, ok := payload["NickName"]; ok && val != nil {
		nickName = val.(string)
	}

	jti := ""
	if val, ok := payload["Jti"]; ok && val != nil {
		jti = val.(string)
	}

	return UserSession{
		Id:       id,
		Userno:   userno,
		NickName: nickName,
		Jti:      jti,
	}
}

// ExtractClaims help to extract the JWT claims
func ExtractClaims(c *gin.Context) jwt.MapClaims {
	claims, exists := c.Get("JWT_PAYLOAD")
	if !exists {
		return make(jwt.MapClaims)
	}

	return claims.(jwt.MapClaims)
}

// ParseToken parse jwt token from hertz context
func ParseToken(c *gin.Context) (*jwt.Token, error) {
	var token string
	var err error

	methods := strings.Split(TokenLookup, ",")
	for _, method := range methods {
		if len(token) > 0 {
			break
		}
		parts := strings.Split(strings.TrimSpace(method), ":")
		k := strings.TrimSpace(parts[0])
		v := strings.TrimSpace(parts[1])
		switch k {
		case "header":
			token, err = jwtFromHeader(c, v)
		case "query":
			token, err = jwtFromQuery(c, v)
		case "cookie":
			token, err = jwtFromCookie(c, v)
		case "param":
			token, err = jwtFromParam(c, v)
		case "form":
			token, err = jwtFromForm(c, v)
		case "referer":
			token, err = jwtFromReferer(c, v)
		}
		if token != "" && err == nil {
			logger.Infof("capture token:%s", token)
			break
		}
	}

	if err != nil {
		return nil, err
	}

	// save token string if valid
	c.Set("JWT_TOKEN", token)

	return jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
		if jwt.GetSigningMethod(SigningAlgorithm) != t.Method {
			return nil, fmt.Errorf("err invalid signingalgorithm")
		}

		var usingPublickkey bool
		switch SigningAlgorithm {
		case "RS256", "RS512", "RS384":
			usingPublickkey = true
		default:
			usingPublickkey = false
		}

		if usingPublickkey {
			return pubKey, nil
		}
		return key, nil
	})
}

func GetClaimsFromJWT(c *gin.Context) (jwt.MapClaims, error) {
	token, err := ParseToken(c)
	if err != nil {
		return nil, err
	}

	claims := jwt.MapClaims{}
	for key, value := range token.Claims.(jwt.MapClaims) {
		claims[key] = value
	}

	return claims, nil
}

func MiddlewareSetIdentity(c *gin.Context) {
	identity := Identity(c)

	if identity != nil {
		c.Set(IdentityKey, identity)
	}
}

func GetXPINKUser(c *gin.Context) UserSession {
	data, ok := c.Get(IdentityKey)
	if data != nil && ok {
		if us, isUs := data.(UserSession); isUs {
			return us
		}
	}
	return UserSession{}
}

func jwtFromHeader(c *gin.Context, key string) (string, error) {
	authHeader := c.Request.Header.Get(key)

	if authHeader == "" {
		return "", ErrEmptyAuthHeader
	}

	parts := strings.SplitN(authHeader, " ", 2)
	if !(len(parts) == 2 && parts[0] == TokenHeadName) {
		return "", ErrInvalidAuthHeader
	}

	return parts[len(parts)-1], nil
}

func jwtFromQuery(c *gin.Context, key string) (string, error) {
	token := c.Query(key)

	if token == "" {
		return "", ErrEmptyQueryToken
	}

	return token, nil
}

func jwtFromCookie(c *gin.Context, key string) (string, error) {
	cookie, _ := c.Cookie(key)

	if cookie == "" {
		return "", ErrEmptyCookieToken
	}

	return cookie, nil
}

func jwtFromParam(c *gin.Context, key string) (string, error) {
	token := c.Param(key)

	if token == "" {
		return "", ErrEmptyParamToken
	}

	return token, nil
}

func jwtFromForm(c *gin.Context, key string) (string, error) {
	token := c.PostForm(key)

	if token == "" {
		return "", ErrEmptyFormToken
	}

	return token, nil
}

func jwtFromReferer(c *gin.Context, key string) (string, error) {
	refererPath := c.GetHeader("Referer")

	if refererPath == "" {
		return "", fmt.Errorf("err empty Referer")
	}
	rul, err := url.Parse(refererPath)
	if err != nil {
		return "", fmt.Errorf("err invalid Referer %s", refererPath)
	}
	token := rul.Query().Get(key)
	if token == "" {
		return "", ErrEmptyQueryToken
	}

	parts := strings.SplitN(token, " ", 2)

	if !(len(parts) == 2 && parts[0] == TokenHeadName) {
		return "", ErrInvalidAuthHeader
	}

	return parts[len(parts)-1], nil
}
