//
// create.go
// Copyright (C) 2025 veypi <i@veypi.com>
// 2025-05-06 15:05
// Distributed under terms of the MIT license.
//

package user

import (
	"encoding/base64"
	"fmt"
	"math/rand"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/veypi/vbase/api/sms"
	"github.com/veypi/vbase/cfg"
	"github.com/veypi/vbase/libs/utils"
	"github.com/veypi/vbase/models"
	"github.com/veypi/vigo"
	"gorm.io/gorm"
)

var _ = Router.Post("/", "用户注册", vigo.SkipBefore, publicLimits, userPost)

type postOpts struct {
	Username   string `json:"username" gorm:"varchar(100);unique;default:not null"  src:"json" desc:"用户名"`
	Code       string `json:"code" gorm:"varchar(128)"  src:"json" desc:"授权码/密码"`
	Phone      string `json:"phone" gorm:"varchar(50);unique;default:null"  src:"json" desc:"手机号"`
	VerifyCode string `json:"verify_code"  src:"json" desc:"验证码"`
	Region     string `json:"region"  src:"json" desc:"区域"`

	Nickname *string `json:"nickname"  src:"json" desc:"昵称"`
	Icon     *string `json:"icon"  src:"json" desc:"头像"`
	Email    *string `json:"email" gorm:"varchar(20);unique;default:null"  src:"json" desc:"邮箱"`
}

func userPost(x *vigo.X, opts *postOpts) (*models.User, error) {
	data := &models.User{}

	if cfg.Config.SMS.Enable {
		data.Phone = opts.Region + opts.Phone
		data.Region = opts.Region
		err := sms.VerifyCode(opts.Phone, opts.VerifyCode, opts.Region, "signup")
		if err != nil {
			return nil, vigo.ErrArgInvalid.WithArgs("verify code").WithString(err.Error())
		}
	}
	data.Username = opts.Username
	data.Code = opts.Code
	data.Salt = utils.RandSeq(16)
	if len(data.Username) < 2 {
		return nil, vigo.ErrArgInvalid.WithArgs("username length")
	}
	code, err := base64.URLEncoding.DecodeString(opts.Code)
	if err != nil || len(code) < 8 {
		return nil, vigo.ErrArgInvalid.WithArgs("code")
	}
	code = utils.PKCS7Padding(code, 32)
	// We need ID for encryption, but it's not generated yet.
	// We can generate it manually here since vigo.Model doesn't auto-generate it before Create
	data.ID = strings.ReplaceAll(uuid.New().String(), "-", "")

	data.Code, err = utils.AesEncrypt([]byte(data.ID), code, []byte(data.Salt))
	if err != nil {
		return nil, vigo.ErrArgInvalid.WithArgs("code")
	}
	ncode, err := utils.AesDecrypt([]byte(data.Code), code, []byte(data.Salt))
	if err != nil || string(ncode) != data.ID {
		return nil, vigo.ErrInternalServer.WithString("code decrypt failed")
	}
	if opts.Nickname != nil {
		data.Nickname = *opts.Nickname
	}
	if opts.Icon != nil {
		data.Icon = *opts.Icon
	} else {
		data.Icon = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220))
	}
	if opts.Email != nil {
		data.Email = *opts.Email
	}
	data.Status = 1
	err = cfg.DB().Transaction(func(tx *gorm.DB) error {
		err := tx.Create(data).Error
		if err != nil {
			return err
		}
		app := &models.App{}
		err = tx.Where("id = ?", cfg.Config.ID).First(app).Error
		if err != nil {
			return err
		}
		status := "ok"
		switch app.Typ {
		case "private":
			return vigo.ErrNotPermitted.WithArgs("not enable register")
		case "apply":
			status = "applying"
		case "public":
		}
		if app.Typ != "public" {
		}

		return tx.Create(&models.AppUser{
			UserID: data.ID,
			AppID:  cfg.Config.ID,
			Status: status,
		}).Error
	})
	return data, err
}
