package cube

import (
	"time"

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

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

	"github.com/pingcap-inc/pchub/internal/pkg/middleware"
	"github.com/pingcap-inc/pchub/internal/pkg/rstore"
	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/rstorage"
	"github.com/pingcap-inc/pchub/pkg/util/ginres"

	srvv1 "github.com/pingcap-inc/pchub/internal/apiserver/service/v1"
	"github.com/pingcap-inc/pchub/internal/apiserver/store"
	"github.com/pingcap-inc/pchub/pkg/log"
)

type CubeController struct {
	srv srvv1.Service
}

func NewCubeController(store store.Factory) *CubeController {
	return &CubeController{
		srv: srvv1.NewService(store),
	}
}

type CubeTokenResData struct {
	Token string  `json:"token"`
	Exp   float64 `json:"exp"`
}

func (p *CubeController) GetToken(c *gin.Context) {
	log.L(c).Info("CubeController.GetToken called.")

	var authTokenInfo middleware.AuthingTokenInfo
	v, ok := c.Get("tokenInfo")
	authTokenInfo, typeOk := v.(middleware.AuthingTokenInfo)
	if !ok || !typeOk {
		ginres.WriteResponse(c, errors.WithCode(code.ErrGetAuthingTokenInfo, ""), nil)

		return
	}

	if authTokenInfo.Uid == "" {
		log.L(c).Error("uid not found")
		ginres.WriteResponse(c, errors.WithCode(code.ErrGetAuthingTokenInfo, "%s", "No valid userId from auth middleware"), nil)

		return
	}

	rStore := &rstorage.RedisCluster{
		KeyPrefix: rstore.CubeTokenKeyPrefix,
	}

	val, err := rStore.GetKey(authTokenInfo.Uid)
	if err != nil {
		if !errors.As(err, &rstorage.ErrKeyNotFound) {
			log.Error(errors.Wrap(err, "error when retrieve cube token key in redis").Error())
		}
	}

	cubeToken := val
	if val == "" { //nolint:nestif // acceptable
		log.Info("cube token not found in redis. Try to generate a new token.")

		nowUnix := time.Now().Unix()
		token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
			"exp":  authTokenInfo.Exp,
			"sub":  authTokenInfo.Uid,
			"name": authTokenInfo.Uname,
			"iat":  nowUnix,
		})
		tokenString, err := token.SignedString([]byte(viper.GetString("cube.secret")))
		if err != nil {
			ginres.WriteResponse(c, errors.WithCode(code.ErrUnknown, ""), nil)

			return
		}
		cubeToken = tokenString
		if authTokenInfo.Exp > float64(nowUnix) {
			deltSec := authTokenInfo.Exp - float64(nowUnix)
			deltSecDu := time.Duration(deltSec)*time.Second - time.Second*2
			if deltSecDu > 0 {
				if err := rStore.SetKey(authTokenInfo.Uid, tokenString, deltSecDu); err != nil {
					log.Errorf("Error when store token into redis: %s", err.Error())
				}
			}
		}
	} else {
		log.Info("cube token found in redis. Use it from redis.")
	}

	ginres.WriteResponse(c, nil, CubeTokenResData{
		Token: cubeToken,
		Exp:   authTokenInfo.Exp,
	})
}
