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

package verification

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

	"github.com/veypi/vbase/cfg"
	"github.com/veypi/vbase/libs/email"
	"github.com/veypi/vbase/libs/sms"
	"github.com/veypi/vbase/models"
	"github.com/veypi/vigo"
	"github.com/veypi/vigo/contrib/requestmeta"
)

// SendRequest 发送验证码请求
type SendRequest struct {
	Type    string `json:"type" src:"json" desc:"类型: email/sms"`
	Target  string `json:"target" src:"json" desc:"邮箱或手机号"`
	Purpose string `json:"purpose" src:"json" desc:"用途: register/login/reset_password/bind"`
}

// SendResponse 发送响应
type SendResponse struct {
	Message string `json:"message"`
}

// sendCode 统一发送验证码
func sendCode(x *vigo.X, req *SendRequest) (*SendResponse, error) {
	// 参数校验
	if req.Type != "email" && req.Type != "sms" {
		return nil, vigo.ErrInvalidArg.WithString("invalid type, must be email or sms")
	}
	if req.Target == "" {
		return nil, vigo.ErrInvalidArg.WithString("target is required")
	}
	if req.Purpose == "" {
		req.Purpose = models.CodePurposeLogin
	}

	// 验证用途是否合法
	validPurposes := map[string]bool{
		models.CodePurposeRegister:      true,
		models.CodePurposeLogin:         true,
		models.CodePurposeResetPassword: true,
		models.CodePurposeBind:          true,
	}
	if !validPurposes[req.Purpose] {
		return nil, vigo.ErrInvalidArg.WithString("invalid purpose")
	}

	db := cfg.DB()

	// 检查服务是否启用（在生成验证码前检查，避免产生无用记录）
	switch req.Type {
	case "email":
		enabled, _ := models.GetSettingBool(models.SettingEmailEnabled)
		if !enabled {
			return nil, vigo.ErrForbidden.WithString("email service not enabled")
		}
	case "sms":
		enabled, _ := models.GetSettingBool(models.SettingSMSEnabled)
		if !enabled {
			return nil, vigo.ErrForbidden.WithString("sms service not enabled")
		}
	}

	// 检查发送频率限制
	interval, _ := models.GetSettingInt(models.SettingCodeSendInterval)
	if interval == 0 {
		interval = 60
	}

	var lastCode models.VerificationCode
	if err := db.Where("target = ? AND type = ?", req.Target, req.Type).
		Order("created_at DESC").First(&lastCode).Error; err == nil {
		// 检查是否在间隔期内
		if time.Since(lastCode.CreatedAt) < time.Duration(interval)*time.Second {
			return nil, vigo.ErrTooManyRequests.WithString(fmt.Sprintf("please wait %d seconds", interval))
		}
	}

	// 检查每日发送次数限制
	// 配置说明: 0=禁用验证码功能, -1=不限制, >0=限制次数
	maxDaily, _ := models.GetSettingInt(models.SettingCodeMaxDailyCount)
	if maxDaily == 0 {
		return nil, vigo.ErrForbidden.WithString("verification code service is disabled")
	}
	if maxDaily > 0 {
		startOfDay := time.Now().Truncate(24 * time.Hour)
		var dailyCount int64
		db.Model(&models.VerificationCode{}).
			Where("target = ? AND type = ? AND created_at >= ?", req.Target, req.Type, startOfDay).
			Count(&dailyCount)
		if int(dailyCount) >= maxDaily {
			return nil, vigo.ErrTooManyRequests.WithString("daily limit exceeded")
		}
	}

	// 生成验证码
	codeLength, _ := models.GetSettingInt(models.SettingCodeLength)
	if codeLength == 0 {
		codeLength = 6
	}
	code := generateCode(codeLength)

	// 过期时间
	expiryMinutes, _ := models.GetSettingInt(models.SettingCodeExpiry)
	if expiryMinutes == 0 {
		expiryMinutes = 5
	}
	expiresAt := time.Now().Add(time.Duration(expiryMinutes) * time.Minute)

	// 保存验证码到数据库
	verification := &models.VerificationCode{
		Target:    req.Target,
		Type:      req.Type,
		Code:      code,
		Purpose:   req.Purpose,
		Status:    models.CodeStatusPending,
		ExpiresAt: expiresAt,
		RemoteIP:  requestmeta.RemoteIP(x),
	}

	if err := db.Create(verification).Error; err != nil {
		return nil, vigo.ErrInternalServer.WithError(err)
	}

	// 发送验证码
	var sendErr error
	switch req.Type {
	case "email":
		sendErr = sendEmailCode(req.Target, code)
	case "sms":
		sendErr = sendSMSCode(req.Target, code)
	}

	if sendErr != nil {
		// 更新状态为失败
		db.Model(verification).Update("status", models.CodeStatusFailed)
		return nil, vigo.ErrInternalServer.WithError(sendErr)
	}

	return &SendResponse{Message: "verification code sent"}, nil
}

// sendEmailCode 发送邮件验证码
func sendEmailCode(target, code string) error {
	// 检查邮件服务是否启用
	enabled, err := models.GetSettingBool(models.SettingEmailEnabled)
	if err != nil || !enabled {
		return fmt.Errorf("email service not enabled")
	}

	// 发送邮件
	if err := email.SendVerificationCode(target, code); err != nil {
		return err
	}

	// 记录日志
	log := &models.EmailLog{
		To:       target,
		Subject:  "验证码",
		Provider: "smtp",
		Status:   "sent",
	}
	cfg.DB().Create(log)

	return nil
}

// sendSMSCode 发送短信验证码
func sendSMSCode(phone, code string) error {
	// 检查短信服务是否启用
	enabled, err := models.GetSettingBool(models.SettingSMSEnabled)
	if err != nil || !enabled {
		return fmt.Errorf("sms service not enabled")
	}

	// 获取短信配置
	provider, _ := models.GetSetting(models.SettingSMSProvider)
	accessKey, _ := models.GetSetting(models.SettingSMSAccessKey)
	accessSecret, _ := models.GetSetting(models.SettingSMSAccessSecret)
	signName, _ := models.GetSetting(models.SettingSMSSignName)
	templateCode, _ := models.GetSetting(models.SettingSMSTemplateCode)

	if accessKey == "" || accessSecret == "" {
		return fmt.Errorf("sms not configured")
	}

	// 创建短信提供商
	smsProvider, err := sms.NewProvider(provider, accessKey, accessSecret, signName, templateCode)
	if err != nil {
		return err
	}

	// 发送短信
	if err := smsProvider.Send(phone, code); err != nil {
		return err
	}

	return nil
}

// generateCode 生成随机验证码
func generateCode(length int) string {
	const digits = "0123456789"
	code := make([]byte, length)
	for i := range code {
		code[i] = digits[rand.Intn(len(digits))]
	}
	return string(code)
}
