package middleware

import (
	"strings"
	"time"

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

	"finance-portal/internal/pkg/code"
	"finance-portal/pkg/errors"
	"finance-portal/pkg/log"
	"finance-portal/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)
		SingleAuth(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
	}
}

func GetAuthingId(c *gin.Context) (string, error) {
	var authTokenInfo AuthingTokenInfo
	v, ok := c.Get("tokenInfo")
	authTokenInfo, typeOk := v.(AuthingTokenInfo)
	if !ok || !typeOk {
		log.L(c).Error("tokenInfo not found")

		return "", errors.WithCode(code.ErrGetAuthingTokenInfo, "")
	}

	if authTokenInfo.Uid == "" {
		log.L(c).Error("uid not found")

		return "", errors.WithCode(code.ErrGetAuthingTokenInfo, "%s", "No valid userId from auth middleware")
	}

	return authTokenInfo.Uid, nil
}

func SingleAuth(token string, c *gin.Context) {
	if token == viper.GetString("secret.token") {
		c.Set(UserNameKey, "single-auth")
	} else {
		ginres.WriteResponse(c, errors.WithCode(code.ErrSignatureInvalid, "Unrecognized token."), nil)
		c.Abort()

		return
	}
}
