package middleware

import (
	"encoding/json"
	"fmt"
	"intent-system/pkg/itypes"
	"net/http"
	"time"

	"github.com/civet148/log"

	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
)

const (
	CLAIM_EXPIRE       = "claim_expire"
	CLAIM_ISSUE_AT     = "claim_iat"
	CLAIM_USER_SESSION = "user_session"
)

const (
	DEFAULT_TOKEN_DURATION = 48 * time.Hour
)

const (
	jwtTokenSecret = "7bdf27cffd5fd105af4efb20b1090bbe"
)

type JwtCode int

const (
	JWT_CODE_SUCCESS             JwtCode = 0
	JWT_CODE_ERROR_CHECK_TOKEN   JwtCode = -1
	JWT_CODE_ERROR_PARSE_TOKEN   JwtCode = -2
	JWT_CODE_ERROR_INVALID_TOKEN JwtCode = -3
	JWT_CODE_ERROR_TOKEN_EXPIRED JwtCode = -4
)

var codeMessages = map[JwtCode]string{
	JWT_CODE_SUCCESS:             "JWT_CODE_SUCCESS",
	JWT_CODE_ERROR_CHECK_TOKEN:   "JWT_CODE_ERROR_CHECK_TOKEN",
	JWT_CODE_ERROR_PARSE_TOKEN:   "JWT_CODE_ERROR_PARSE_TOKEN",
	JWT_CODE_ERROR_INVALID_TOKEN: "JWT_CODE_ERROR_INVALID_TOKEN",
	JWT_CODE_ERROR_TOKEN_EXPIRED: "JWT_CODE_ERROR_TOKEN_EXPIRED",
}

func (j JwtCode) GoString() string {
	return j.String()
}

func (j JwtCode) String() string {
	strMessage, ok := codeMessages[j]
	if ok {
		return strMessage
	}
	return fmt.Sprintf("JWT_CODE_UNKONWN<%d>", j)
}

func JWT() gin.HandlerFunc {
	return func(c *gin.Context) {
		var data interface{}
		if err := ParseToken(c); err != nil {
			c.JSON(http.StatusUnauthorized, itypes.HttpResponse{
				Header: itypes.HttpHeader{
					Code:    itypes.CODE_UNAUTHORIZED,
					Message: "unauthorized",
					Count:   0,
				},
				Data: data,
			})
			log.Errorf("[JWT] token parse failed, error [%s]", err.Error())
			c.Abort()
			return
		}

		c.Next()
	}
}

// generate JWT token
func GenerateToken(session interface{}, duration ...interface{}) (token string, err error) {

	var d time.Duration
	var claims = make(jwt.MapClaims)

	if len(duration) == 0 {
		d = DEFAULT_TOKEN_DURATION
	} else {
		var ok bool
		if d, ok = duration[0].(time.Duration); !ok {
			d = DEFAULT_TOKEN_DURATION
		}
	}
	var data []byte
	data, err = json.Marshal(session)
	if err != nil {
		return token, log.Errorf(err.Error())
	}
	sign := jwt.New(jwt.SigningMethodHS256)
	claims[CLAIM_EXPIRE] = time.Now().Add(d).Unix()
	claims[CLAIM_ISSUE_AT] = time.Now().Unix()
	claims[CLAIM_USER_SESSION] = string(data)
	sign.Claims = claims

	token, err = sign.SignedString([]byte(jwtTokenSecret))
	return token, err
}

// parse JWT token claims
func ParseToken(c *gin.Context) error {
	strAuthToken := GetAuthToken(c)
	if strAuthToken == "" {
		return log.Errorf("[JWT] request header have no any key '%s' or '%s'", itypes.HEADER_AUTH_TOKEN, itypes.HEADER_AUTHORIZATION)
	}
	claims, err := ParseTokenClaims(strAuthToken)
	if err != nil {
		return log.Errorf(err.Error())
	}
	c.Keys = make(map[string]interface{})
	c.Keys[CLAIM_EXPIRE] = int64(claims[CLAIM_EXPIRE].(float64))
	if c.Keys[CLAIM_EXPIRE].(int64) < time.Now().Unix() {
		return log.Errorf("[JWT] token [%s] expired at %v\n", strAuthToken, c.Keys[CLAIM_EXPIRE])
	}

	c.Keys[CLAIM_EXPIRE] = int64(claims[CLAIM_EXPIRE].(float64))
	c.Keys[CLAIM_ISSUE_AT] = int64(claims[CLAIM_ISSUE_AT].(float64))
	c.Keys[CLAIM_USER_SESSION] = claims[CLAIM_USER_SESSION].(string)
	return nil
}

func ParseTokenClaims(strAuthToken string) (jwt.MapClaims, error) {
	token, err := jwt.Parse(strAuthToken, func(*jwt.Token) (interface{}, error) {
		return []byte(jwtTokenSecret), nil
	})
	if err != nil {
		return jwt.MapClaims{}, log.Errorf("[JWT] parse token error [%s]", err)
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return jwt.MapClaims{}, log.Errorf("[JWT] parse token error: no claims found")
	}
	return claims, nil
}

func GetAuthToken(c *gin.Context) string {
	strToken := c.Request.Header.Get(itypes.HEADER_AUTH_TOKEN)
	if strToken == "" {
		strToken = c.Request.Header.Get(itypes.HEADER_AUTHORIZATION)
	}
	return strToken
}

func GetAuthSessionFromToken(strAuthToken string, session interface{}) error {
	claims, err := ParseTokenClaims(strAuthToken)
	if err != nil {
		return log.Errorf(err.Error())
	}
	strSessionJson := claims[CLAIM_USER_SESSION].(string)
	err = json.Unmarshal([]byte(strSessionJson), session)
	if err != nil {
		return log.Errorf(err.Error())
	}
	return nil
}

func GetAuthSessionFromContext(c *gin.Context, session interface{}) error {
	strAuthToken := GetAuthToken(c)
	return GetAuthSessionFromToken(strAuthToken, session)
}
