package middleware

import (
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v4"
	"github.com/spf13/viper"

	"github.com/pingcap-inc/pingcat/internal/pkg/code"

	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/util/ginres"
)

const UserNameKey = "username"

type AuthingTokenInfo struct {
	Uid            string
	Uname          string
	Exp            float64
	Email          string
	OriginalJWTStr string
}

func Auth() gin.HandlerFunc {
	return func(c *gin.Context) {
		authHeader := strings.SplitN(c.Request.Header.Get("Authorization"), " ", 2)
		const shouldAuthHeaderCount = 2
		if len(authHeader) != shouldAuthHeaderCount {
			ginres.WriteResponse(
				c,
				errors.WithCode(code.ErrInvalidAuthHeader, "Authorization header format is wrong."),
				nil,
			)
			c.Abort()

			return
		}

		if authHeader[0] != "Bearer" {
			ginres.WriteResponse(c, errors.WithCode(code.ErrSignatureInvalid, "Unrecognized Authorization header."), nil)
			c.Abort()

			return
		}

		jwtAuth(authHeader[1], c)

		c.Next()
	}
}

func jwtAuth(jwtStr string, c *gin.Context) {
	token, err := jwt.Parse(jwtStr, func(token *jwt.Token) (interface{}, error) {
		// https://github.com/golang-jwt/jwt/issues/98
		// https://github.com/deso-protocol/backend/pull/169/commits/b7d87f8574fef706d95ccb93607c9fd9f354cdc6
		mapClaims, ok := token.Claims.(jwt.MapClaims)
		if !ok {
			return nil, errors.WithCode(code.ErrSignatureInvalid, "Unrecognized token.")
		}
		delete(mapClaims, "iat")

		tempSecret := []byte(viper.GetString("authing.appSecret"))

		return tempSecret, nil
	})
	if err != nil {
		ginres.WriteResponse(c, errors.WithCode(code.ErrSignatureInvalid, "Unrecognized token."), nil)
		c.Abort()

		return
	}

	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		exp, ok := claims["exp"].(float64)
		if !ok {
			ginres.WriteResponse(c, errors.WithCode(code.ErrSignatureInvalid, "No exp or invalid exp."), nil)
			c.Abort()

			return
		}
		if time.Now().Unix() > int64(exp) {
			ginres.WriteResponse(c, errors.WithCode(code.ErrExpired, "Token expired."), nil)
			c.Abort()

			return
		}

		email, eok := claims["email"].(string)
		sub, sok := claims["sub"].(string)
		name, nok := claims["name"].(string)
		if !eok || !sok || !nok {
			ginres.WriteResponse(c, errors.WithCode(code.ErrTokenPayload, "Token fields(email,sub,name) missing."), nil)
			c.Abort()

			return
		}

		c.Set("tokenInfo", AuthingTokenInfo{
			Uid:            sub,
			Uname:          name,
			Exp:            exp,
			Email:          email,
			OriginalJWTStr: jwtStr,
		})

		c.Set(UserNameKey, name)
	} else {
		ginres.WriteResponse(c, errors.WithCode(code.ErrSignatureInvalid, "Invalid token."), nil)
		c.Abort()

		return
	}
}
