package jwts

import (
	"LAPP_ACURA_MOM_BACKEND/conf"
	"LAPP_ACURA_MOM_BACKEND/global"
	"LAPP_ACURA_MOM_BACKEND/web/supports"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/kataras/golog"
	"log"
	"strings"

	"time"

	"sync"

	"github.com/kataras/iris/v12"
)

// iris provides some basic middleware, most for your learning courve.
// You can use any net/http compatible middleware with iris.FromStd wrapper.
//
// JWT net/http video tutorial for golang newcomers: https://www.youtube.com/watch?v=dgJFeqeXVKw
//
// Unlike the other middleware, this middleware was cloned from external source: https://github.com/auth0/go-jwt-middleware
// (because it used "context" to define the user but we don't need that so a simple iris.FromStd wouldn't work as expected.)
// jwt_test.go also didn't created by me:
// 28 Jul 2016
// @heralight heralight add jwts unit test.
//
// So if this doesn't works for you just try other net/http compatible middleware and bind it via `iris.FromStd(myHandlerWithNext)`,
// It's here for your learning curve.

type (
	// A function called whenever an error is encountered
	errorHandler func(iris.Context, string)

	// TokenExtractor is a function that takes a context as input and returns
	// either a token or an error.  An error should only be returned if an attempt
	// to specify a token was found, but the information was somehow incorrectly
	// formed.  In the case where a token is simply not present, this should not
	// be treated as an error.  An empty string should be returned in that case.
	TokenExtractor func(iris.Context) (string, error)

	// Middleware the middleware for JSON Web tokens authentication method
	Jwts struct {
		Config Config
	}
)

var (
	jwts *Jwts
	lock sync.Mutex
)

// Serve the middleware's action
func Serve(ctx iris.Context) bool {
	ConfigJWT()
	if err := jwts.CheckJWT(ctx); err != nil {
		//supports.Unauthorized(ctx, supports.Token_failur, nil)
		//ctx.StopExecution()
		golog.Errorf("Check jwt error, %s", err)
		return false
	}
	return true
	// If everything ok then call next.
	//ctx.Next()
}

// 解析token的信息为当前用户
func ParseToken(ctx iris.Context) (*global.User, bool) {
	mapClaims := (jwts.Get(ctx).Claims).(jwt.MapClaims)

	plantNr, ok1 := mapClaims["plantNr"].(float64)
	userId, ok2 := mapClaims["userId"].(string)
	role, ok3 := mapClaims["role"].(string)
	roleId, ok4 := mapClaims["roleId"].(float64)
	if !ok1 || !ok2 || !ok3 || !ok4 {
		supports.Error(ctx, iris.StatusInternalServerError, supports.TokenParseFailur, nil)
		return nil, false
	}

	user := global.User{
		PlantNr: int(plantNr),
		UserId:  userId,
		Role:    role,
		RoleId:  int(roleId),
	}
	return &user, true
}

// 解析token的信息为当前用户和Token签名
func ParseTokenAndSignature(ctx iris.Context) (*global.User, string, bool) {
	token := jwts.Get(ctx)
	mapClaims := (token.Claims).(jwt.MapClaims)

	plantNr, ok1 := mapClaims["plantNr"].(float64)
	userId, ok2 := mapClaims["userId"].(string)
	role, ok3 := mapClaims["role"].(string)
	roleId, ok4 := mapClaims["roleId"].(float64)

	if !ok1 || !ok2 || !ok3 || !ok4 {
		supports.Error(ctx, iris.StatusInternalServerError, supports.TokenParseFailur, nil)
		return nil, "", false
	}

	user := global.User{
		PlantNr: int(plantNr),
		UserId:  userId,
		Role:    role,
		RoleId:  int(roleId),
	}
	return &user, token.Signature, true
}

// 解析token的信息为当前用户
func GetToken(ctx iris.Context) *jwt.Token {
	return jwts.Get(ctx)
}

// below 3 method is get token from url
// FromAuthHeader is a "TokenExtractor" that takes a give context and extracts
// the JWT token from the Authorization header.
func FromAuthHeader(ctx iris.Context) (string, error) {
	authHeader := ctx.GetHeader("Authorization")
	if authHeader == "" {
		return "", nil // No error, just no token
	}

	// TODO: Make this a bit more robust, parsing-wise
	authHeaderParts := strings.Split(authHeader, " ")
	if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" {
		return "", fmt.Errorf("Authorization header format must be Bearer {token}")
	}

	return authHeaderParts[1], nil
}

// below 3 method is get token from url
// FromParameter returns a function that extracts the token from the specified
// query string parameter
func FromParameter(param string) TokenExtractor {
	return func(ctx iris.Context) (string, error) {
		return ctx.URLParam(param), nil
	}
}

// below 3 method is get token from url
// FromFirst returns a function that runs multiple token extractors and takes the
// first token it finds
func FromFirst(extractors ...TokenExtractor) TokenExtractor {
	return func(ctx iris.Context) (string, error) {
		for _, ex := range extractors {
			token, err := ex(ctx)
			if err != nil {
				return "", err
			}
			if token != "" {
				return token, nil
			}
		}
		return "", nil
	}
}

func (m *Jwts) logf(format string, args ...interface{}) {
	if m.Config.Debug {
		log.Printf(format, args...)
	}
}

// Get returns the user (&token) information for this client/request
func (m *Jwts) Get(ctx iris.Context) *jwt.Token {
	return ctx.Values().Get(m.Config.ContextKey).(*jwt.Token)
}

// CheckJWT the main functionality, checks for token
func (m *Jwts) CheckJWT(ctx iris.Context) error {
	if !m.Config.EnableAuthOnOptions {
		if ctx.Method() == iris.MethodOptions {
			return nil
		}
	}

	// Use the specified token extractor to extract a token from the request
	token, err := m.Config.Extractor(ctx)
	// If an error occurs, call the error handler and return an error
	if err != nil {
		m.logf("Error extracting JWT: %v", err)
		m.Config.ErrorHandler(ctx, supports.TokenExactFailur)
		return fmt.Errorf("Error extracting token: %v", err)
	}

	// If the token is empty...
	if token == "" {
		// Check if it was required
		if m.Config.CredentialsOptional {
			m.logf("  No credentials found (CredentialsOptional=true)")
			// No error, just no token (and that is ok given that CredentialsOptional is true)
			return nil
		}

		m.logf("  Error: No credentials found (CredentialsOptional=false)")
		// If we get here, the required token is missing
		m.Config.ErrorHandler(ctx, supports.TokenParseFailurAndEmpty)
		return fmt.Errorf(supports.TokenParseFailurAndEmpty)
	}

	// Now parse the token

	parsedToken, err := jwt.Parse(token, m.Config.ValidationKeyGetter)

	// Check if there was an error in parsing...
	if err != nil {
		m.logf("Error parsing token1: %v", err)
		m.Config.ErrorHandler(ctx, supports.TokenExpire)
		return fmt.Errorf("Error parsing token2: %v", err)
	}

	if m.Config.SigningMethod != nil && m.Config.SigningMethod.Alg() != parsedToken.Header["alg"] {
		message := fmt.Sprintf("Expected %s signing method but token specified %s",
			m.Config.SigningMethod.Alg(),
			parsedToken.Header["alg"])
		m.logf("Error validating token algorithm: %s", message)
		m.Config.ErrorHandler(ctx, supports.TokenParseFailur) // 算法错误
		return fmt.Errorf("Error validating token algorithm: %s", message)
	}

	// Check if the parsed token is valid...
	if !parsedToken.Valid {
		m.logf(supports.TokenParseFailurAndInvalid)
		m.Config.ErrorHandler(ctx, supports.TokenParseFailurAndInvalid)
		return fmt.Errorf(supports.TokenParseFailurAndInvalid)
	}

	if m.Config.Expiration {
		if claims, ok := parsedToken.Claims.(jwt.MapClaims); ok {
			if expired := claims.VerifyExpiresAt(time.Now().Unix(), true); !expired {
				return fmt.Errorf(supports.TokenExpire)
			}
		}
	}

	//m.logf("JWT: %v", parsedToken)

	// If we get here, everything worked and we can set the
	// user property in context.
	ctx.Values().Set(m.Config.ContextKey, parsedToken)

	return nil
}

// jwt中间件配置
func ConfigJWT() {
	if jwts != nil {
		return
	}

	lock.Lock()
	defer lock.Unlock()

	if jwts != nil {
		return
	}

	c := Config{
		ContextKey: DefaultContextKey,
		//这个方法将验证jwt的token
		ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
			//自己加密的秘钥或者说盐值
			return []byte(conf.AppConfig.Other.Secret), nil
		},
		//设置后，中间件会验证令牌是否使用特定的签名算法进行签名
		//如果签名方法不是常量，则可以使用ValidationKeyGetter回调来实现其他检查
		//重要的是要避免此处的安全问题：https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
		//加密的方式
		SigningMethod: jwt.SigningMethodHS256,
		//验证未通过错误处理方式
		ErrorHandler: func(ctx iris.Context, errMsg string) {
			supports.Error(ctx, iris.StatusUnauthorized, errMsg, nil)
		},
		// 指定func用于提取请求中的token
		Extractor: FromAuthHeader,
		// if the token was expired, expiration error will be returned
		Expiration:          true,
		Debug:               true,
		EnableAuthOnOptions: false,
	}
	jwts = &Jwts{Config: c}
}

type Claims struct {
	PlantNr int    `json:"plantNr"`
	Userid  string `json:"userId"`
	Role    string `json:"role"`
	RoleId  int    `json:"roleId"`
	jwt.StandardClaims
}

// 在登录成功的时候生成token
func GenerateToken(user *global.User) (string, error) {
	//expireTime := time.Now().Add(60 * time.Second)
	expireTime := time.Now().Add(time.Duration(conf.AppConfig.Other.JWTTimeout) * time.Second)

	claims := Claims{
		user.PlantNr,
		user.UserId,
		user.Role,
		user.RoleId,
		jwt.StandardClaims{
			ExpiresAt: expireTime.Unix(),
			Issuer:    "iris-casbins-jwt",
		},
	}
	fmt.Println(claims)
	tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	token, err := tokenClaims.SignedString([]byte(conf.AppConfig.Other.Secret))
	return token, err
}
