package auth

import (
	"context"
	"encoding/base64"
	"errors"
	"github.com/goccy/go-json"
	"gofiber.study.skcks.cn/common/logger"
	"gofiber.study.skcks.cn/common/utils"
	"gofiber.study.skcks.cn/global"
	"gofiber.study.skcks.cn/middleware"
	"gofiber.study.skcks.cn/model/dto"
	"gofiber.study.skcks.cn/model/generic/models"
	"gofiber.study.skcks.cn/model/vo"
	"time"
)

var (
	PasswordIsEmpty     = errors.New("密码不能为空")
	Failed              = errors.New("账号或密码错误")
	InvalidRefreshToken = errors.New("刷新令牌已失效")
)

const (
	Separator          = ":"
	RefreshTokenPrefix = "RefreshToken" + Separator
)

type Service struct {
}

var Services *Service

func InitService() {
	Services = &Service{}
}

// generateAndSaveRefreshToken
//
// 生成并保存 refreshToken 刷新令牌
func (s *Service) generateAndSaveRefreshToken(user *models.User) (refreshTokenB64 string, err error) {
	refreshTokenB64, err = global.GetNanoId()
	if err != nil {
		return
	}

	refreshTokenB64 = user.Id + Separator + refreshTokenB64

	expire := time.Duration(global.Config.Jwt.Expire*2) * time.Second
	ctx := context.Background()
	global.Redis.Set(ctx, RefreshTokenPrefix+refreshTokenB64, utils.Json(user), expire)

	// base64 加密
	return base64.StdEncoding.EncodeToString([]byte(refreshTokenB64)), err
}

func (s *Service) fromRefreshTokenGetUserCache(refreshTokenB64 string) (user *models.User, err error) {
	refreshTokenBytes, err := base64.StdEncoding.DecodeString(refreshTokenB64)
	if err != nil {
		return nil, InvalidRefreshToken
	}

	refreshToken := RefreshTokenPrefix + string(refreshTokenBytes)

	ctx := context.Background()
	data, err := global.Redis.Get(ctx, refreshToken).Result()
	if err != nil {
		return nil, InvalidRefreshToken
	}
	global.Redis.Del(ctx, refreshToken)
	cache := &models.User{}
	err = json.Unmarshal([]byte(data), cache)
	if err != nil {
		return nil, InvalidRefreshToken
	}

	user = &models.User{Id: cache.Id, Account: cache.Account}
	exist, err := global.DataSources.Get(user)
	if !exist {
		logger.Log.Infof("未能从 %s 找到用户信息", refreshToken)
		return nil, InvalidRefreshToken
	}
	return
}

func (s *Service) getUserClaims(user *models.User) global.UserClaims {
	return global.UserClaims{
		Id:       user.Id,
		Account:  user.Account,
		Identify: "user" + middleware.CasBinSeparator + user.Account,
	}
}

func (s *Service) Login(login *dto.Login) (result *vo.Login, err error) {
	err = global.ValidateStruct(login)
	if err != nil {
		return nil, err
	}

	user := &models.User{Account: login.Account, Password: login.Password}
	exist, err := global.DataSources.Get(user)
	if err != nil {
		return
	}

	if !exist {
		return nil, Failed
	}

	token, err := global.GetToken(s.getUserClaims(user))

	if err != nil {
		return
	}

	refreshToken, err := s.generateAndSaveRefreshToken(user)

	_, _ = global.DataSources.Update(user)

	return &vo.Login{
		Token:        token,
		RefreshToken: refreshToken,
	}, err
}

func (s *Service) RefreshToken(refreshTokenB64 string) (result *vo.Login, err error) {
	user, err := s.fromRefreshTokenGetUserCache(refreshTokenB64)
	if err != nil {
		return
	}

	token, err := global.GetToken(s.getUserClaims(user))

	if err != nil {
		return
	}

	refreshToken, err := s.generateAndSaveRefreshToken(user)

	return &vo.Login{
		Token:        token,
		RefreshToken: refreshToken,
	}, err
}
