//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2025-03-04 16:08:06
// Distributed under terms of the MIT license.
//

package auth

import (
	"fmt"
	"math/rand"
	"regexp"
	"strings"
	"time"

	"github.com/veypi/vbase/cfg"
	"github.com/veypi/vbase/libs/crypto"
	"github.com/veypi/vbase/models"
	"github.com/veypi/vigo"
	"github.com/veypi/vigo/logv"
	"gorm.io/gorm"
)

// Email 正则表达式
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)

// RegisterRequest 注册请求
type RegisterRequest struct {
	Username string `json:"username" src:"json" desc:"用户名"`
	Password string `json:"password" src:"json" desc:"密码"`
	Email    string `json:"email,omitempty" src:"json" desc:"邮箱"`
	Phone    string `json:"phone,omitempty" src:"json" desc:"手机号"`
	Nickname string `json:"nickname,omitempty" src:"json" desc:"昵称"`
}

// register 用户注册
func register(x *vigo.X, req *RegisterRequest) (*AuthResponse, error) {
	// 验证用户名
	if strings.TrimSpace(req.Username) == "" {
		return nil, vigo.ErrInvalidArg.WithString("username is required")
	}
	if len(req.Username) < 3 || len(req.Username) > 50 {
		return nil, vigo.ErrInvalidArg.WithString("username must be between 3 and 50 characters")
	}
	// 用户名只能包含字母、数字和下划线
	if !regexp.MustCompile(`^[a-zA-Z0-9_]+$`).MatchString(req.Username) {
		return nil, vigo.ErrInvalidArg.WithString("username can only contain letters, numbers and underscores")
	}

	// 验证密码
	if strings.TrimSpace(req.Password) == "" {
		return nil, vigo.ErrInvalidArg.WithString("password is required")
	}
	if len(req.Password) < 8 {
		return nil, vigo.ErrInvalidArg.WithString("password must be at least 8 characters")
	}

	// 验证邮箱格式
	if req.Email != "" && !emailRegex.MatchString(req.Email) {
		return nil, vigo.ErrInvalidArg.WithString("invalid email format")
	}

	// 检查注册配置
	requireEmail, _ := models.GetSettingBool(models.SettingAuthRegRequireEmail)
	requirePhone, _ := models.GetSettingBool(models.SettingAuthRegRequirePhone)

	// 校验必填字段
	if requireEmail && req.Email == "" {
		return nil, vigo.ErrInvalidArg.WithString("email is required")
	}
	if requirePhone && req.Phone == "" {
		return nil, vigo.ErrInvalidArg.WithString("phone is required")
	}

	// 使用事务处理注册，防止并发创建多个首个管理员用户
	var user *models.User
	err := cfg.DB().Transaction(func(tx *gorm.DB) error {
		// 检查是否是第一个用户（在事务内检查，带锁）
		var userCount int64
		if err := tx.Model(&models.User{}).Count(&userCount).Error; err != nil {
			return err
		}

		// 检查用户名是否已存在
		var count int64
		if err := tx.Model(&models.User{}).Where("username = ?", req.Username).Count(&count).Error; err != nil {
			return err
		}
		if count > 0 {
			return vigo.ErrInvalidArg.WithArgs("username already exists")
		}

		// 检查邮箱是否已存在
		if req.Email != "" {
			count = 0
			if err := tx.Model(&models.User{}).Where("email = ?", req.Email).Count(&count).Error; err != nil {
				return err
			}
			if count > 0 {
				return vigo.ErrInvalidArg.WithArgs("email already exists")
			}
		}

		// 检查手机是否已存在
		if req.Phone != "" {
			count = 0
			if err := tx.Model(&models.User{}).Where("phone = ?", req.Phone).Count(&count).Error; err != nil {
				return err
			}
			if count > 0 {
				return vigo.ErrInvalidArg.WithArgs("phone already exists")
			}
		}

		// 哈希密码
		hashedPassword, err := crypto.HashPassword(req.Password, 12)
		if err != nil {
			return err
		}

		// 创建用户
		var email *string
		if req.Email != "" {
			email = &req.Email
		}
		var phone *string
		if req.Phone != "" {
			phone = &req.Phone
		}

		user = &models.User{
			Username: req.Username,
			Password: hashedPassword,
			Email:    email,
			Phone:    phone,
			Nickname: req.Nickname,
			Status:   models.UserStatusActive,
		}

		if user.Nickname == "" {
			user.Nickname = user.Username
		}

		// 生成随机头像
		user.Avatar = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220))

		// 第一个用户固定 ID 为 admin
		if userCount == 0 {
			user.ID = "admin"
		}

		if err := tx.Create(user).Error; err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return nil, err
	}
	if err := cfg.OnUserCreate(user.ID); err != nil {
		logv.Warn().Msgf("user create hook failed: %s", err)
	}

	// 授予角色（事务外，因为事务已确保用户创建成功）
	roleCode := "user"
	if user.ID == "admin" {
		roleCode = "admin"
	}
	if err := cfg.Auth.GrantRole(x.Context(), user.ID, roleCode); err != nil {
		return nil, vigo.ErrInternalServer.WithError(err)
	}

	// 生成token
	return generateAuthResponseForUser(x, user)
}
