package middleware

import (
	"fmt"
	"strings"
	"time"

	"github.com/Authing/authing-go-sdk/lib/authentication"
	"github.com/gin-gonic/gin"
	jwt "github.com/golang-jwt/jwt/v4"
	"github.com/marmotedu/errors"
	"github.com/marmotedu/log"
	"github.com/pingcap-inc/pinsights/internal/apiserver/store"
	"github.com/pingcap-inc/pinsights/internal/pkg/code"
	"github.com/pingcap-inc/pinsights/pkg/storage"
	"github.com/pingcap-inc/pinsights/pkg/util/core"
	"github.com/spf13/viper"
)

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

const hasPermission = "y"

var WHITE_LIST = map[string]string{
	"liuqi@pingcap.com":        hasPermission,
	"alex.fan@pingcap.com":     hasPermission,
	"ardelle.f@pingcap.com":    hasPermission,
	"cuiqiu@pingcap.com":       hasPermission,
	"zhuwei@pingcap.com":       hasPermission,
	"huang@pingcap.com":        hasPermission,
	"mengjie.yu@pingcap.com":   hasPermission,
	"jinkun@pingcap.com":       hasPermission,
	"liuy@pingcap.com":         hasPermission,
	"shenli@pingcap.com":       hasPermission,
	"tl@pingcap.com":           hasPermission,
	"wink@pingcap.com":         hasPermission,
	"yujun@pingcap.com":        hasPermission,
	"maggiexu@pingcap.com":     hasPermission,
	"xiaojing.yin@pingcap.com": hasPermission,
	"liang.yang@pingcap.com":   hasPermission,
	"sophie.wang@pingcap.com":  hasPermission,
	"barry@pingcap.com":        hasPermission,
	"chenjianlin@pingcap.com":  hasPermission,
	"yumin.li@pingcap.com":     hasPermission,
	"xuwei@pingcap.com":        hasPermission,
	"yaodongsen@pingcap.com":   hasPermission,
	"xiaomian.liu@pingcap.com": hasPermission,
	//"shenjing.zhao@pingcap.com": hasPermission,
}

const WHITE_LIST_MID_KEY string = "white-list-"
const EMAIL_LARKID_KEY = "email-larkid-"
const LARKID_WHITE_LIST_MID_KEY = "white-list-larkid-"

const authHeaderCount = 2

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

			return
		}

		if authHeader[0] != "Bearer" {
			core.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 := token.Claims.(jwt.MapClaims)
		delete(mapClaims, "iat")

		tempSecret := []byte(viper.GetString("authing.appSecret"))
		return tempSecret, nil
	})
	if err != nil {
		core.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 {
			core.WriteResponse(c, errors.WithCode(code.ErrSignatureInvalid, "No exp or invalid exp."), nil)
			c.Abort()

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

			return
		}

		rStore := &storage.RedisCluster{
			KeyPrefix: store.P7SKeyPrefix,
		}

		inWhiteList := true
		email, ok := claims["email"].(string)
		inWhiteList = ok && inWhiteList

		_, inHardWhiteList := WHITE_LIST[email]
		_, err := rStore.GetKey(WHITE_LIST_MID_KEY + email)
		if err != nil {
			inWhiteList = inHardWhiteList && inWhiteList
		}

		if !inWhiteList {
			isLAuth := isInLarkWhiteList(rStore, jwtStr, email)

			if !isLAuth {
				core.WriteResponse(c, errors.WithCode(code.ErrNotInWhiteList, "User not in whitelist."), nil)
				c.Abort()
				return
			}
		}

		// if valid
		var uid string
		if sub, ok := claims["sub"].(string); ok {
			uid = sub
		}
		var uname string
		if name, ok := claims["name"].(string); ok {
			uname = name
		}
		c.Set("AUTHING_TOKEN_INFO", AuthingTokenInfo{
			Uid:            uid,
			Uname:          uname,
			Exp:            exp,
			Email:          email,
			OriginalJWTStr: jwtStr,
		})
	} else {
		core.WriteResponse(c, errors.WithCode(code.ErrSignatureInvalid, "Invalid token."), nil)
		c.Abort()

		return
	}
}

func isInLarkWhiteList(rStore *storage.RedisCluster, jwtStr string, email string) bool {
	log.Info("Not in hardcoding whitelist or redis email list, try to judge by larkid whitelist.")
	larkuid, err := rStore.GetKey(EMAIL_LARKID_KEY + email)
	if err != nil {
		log.Info("Not in redis larkid whitelist, try to get larkid from authing client.")

		authenticationClient := authentication.NewClient(viper.GetString("authing.appId"), viper.GetString("authing.appSecret"), "https://pinsights.authing.pingcap.net")
		resp, err := authenticationClient.GetCurrentUser(&jwtStr)
		if err != nil {
			log.Error(errors.WithMessage(err, "Can not get current userInfo from authing").Error())
			return false
		}

		for _, idt := range resp.Identities {
			fmt.Printf("\n%v\n", *idt)
			if *idt.Provider == "lark" && *idt.Type == "generic" {
				larkuid = *idt.UserIdInIdp
				_ = rStore.SetKey(EMAIL_LARKID_KEY+email, larkuid, 0)
			}
		}
	}

	_, err = rStore.GetKey(LARKID_WHITE_LIST_MID_KEY + larkuid)
	if err == nil {
		log.Info("Whitelist by lark, email: " + email)
		return true
	}

	return false
}
