//
// login.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 2025-05-12 17:35
// Distributed under terms of the MIT license.
//

package user

import (
	"encoding/base64"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/veypi/vbase/api/sms"
	"github.com/veypi/vbase/cfg"
	"github.com/veypi/vbase/libs/auth"
	"github.com/veypi/vbase/libs/utils"
	"github.com/veypi/vbase/models"
	"github.com/veypi/vigo"
	"github.com/veypi/vigo/contrib/limiter"
	"github.com/veypi/vigo/logv"
)

var publicLimits = limiter.NewAdvancedRequestLimiter(time.Minute*5, 20, time.Second*3, nil).Limit

var _ = Router.Post("/login", "用户登录",
	vigo.SkipBefore, publicLimits,
	userLogin)

type loginOpts struct {
	UserName *string `json:"username"  src:"json" desc:"用户名"`
	Code     *string `json:"code"  src:"json" desc:"密码/授权码"`

	VerifyCode *string `json:"verify_code"  src:"json" desc:"验证码"`
	Region     *string `json:"region"  src:"json" desc:"区域"`
	Phone      *string `json:"phone"  src:"json" desc:"手机号"`

	Email  *string `json:"email"  src:"json" desc:"邮箱"`
	Type   *string `json:"type"  src:"json" desc:"登录类型"`
	AppID  *string `json:"app_id"  src:"json" desc:"应用ID"`
	Device *string `json:"device"  src:"json" desc:"设备信息"`
}

func userLogin(x *vigo.X, opts *loginOpts) (string, error) {
	// Implement login logic here
	// For example, validate user credentials and return a token
	user := &models.User{}
	query := cfg.DB()
	typ := ""
	if opts.Type != nil {
		typ = *opts.Type
	}
	switch typ {
	case "phone":
		query = query.Where("phone = ?", *opts.Region+*opts.Phone)
	case "email":
		query = query.Where("email = ?", *opts.Email)
	default:
		query = query.Where("username = ?", *opts.UserName)
	}
	err := query.First(user).Error
	if err != nil {
		return "", vigo.ErrNotFound
	}
	logv.Info().Str("user", user.ID).Msg("login")
	if opts.VerifyCode != nil {
		err = sms.VerifyCode(*opts.Phone, *opts.VerifyCode, *opts.Region, "signin")
		if err != nil {
			logv.Warn().Msgf("verify code: %v", err)
			return "", vigo.ErrNotAuthorized.WithError(err)
		}
	} else {
		code, err := base64.URLEncoding.DecodeString(*opts.Code)
		if err != nil {
			return "", vigo.ErrArgInvalid.WithArgs("code")
		}

		ncode, err := utils.AesDecrypt([]byte(user.Code), utils.PKCS7Padding(code, 32), []byte(user.Salt))
		if err != nil || string(ncode) != user.ID {
			return "", vigo.ErrNotAuthorized
		}
	}
	aid := cfg.Config.ID
	if opts.AppID != nil && *opts.AppID != "" {
		aid = *opts.AppID
	}

	data := &models.Token{}

	data.UserID = user.ID
	data.AppID = aid
	data.ExpiredAt = time.Now().Add(time.Hour * 72)
	if opts.Device != nil {
		data.Device = *opts.Device
	}

	err = cfg.DB().Create(data).Error
	if err != nil {
		return "", err
	}

	claim := &auth.Claims{}
	claim.ID = data.ID
	claim.UID = user.ID
	claim.AID = aid
	claim.Name = user.Username
	claim.Icon = user.Icon
	claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt)
	claim.IssuedAt = jwt.NewNumericDate(time.Now())
	claim.Issuer = cfg.Config.ID

	return auth.GenJwt(claim)
}
