// 邮箱验证码登录（桌面端第二登录方式）：
//   POST /v1/auth/email/code  发送 6 位验证码（60s 冷却，码 10 分钟有效）
//   POST /v1/auth/email       校验验证码 → 建号/登录 → JWT
// 邮件发送经 Mailer 接口；SMTP 未配置时装配 MockMailer（验证码打日志，供联调）。
package auth

import (
	"context"
	"crypto/rand"
	"crypto/subtle"
	"errors"
	"log/slog"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
	"gorm.io/gorm"

	"dudu/server/internal/store"
	"dudu/server/pkg/protocol"
)

const (
	emailCodeTTL      = 10 * time.Minute
	emailCodeCooldown = 60 * time.Second
)

// Mailer 验证码邮件发送。
type Mailer interface {
	SendCode(ctx context.Context, email, code string) error
}

// MockMailer 开发期 mock：验证码打日志不真发（⚠️ 上线前必须替换 SMTP 实现）。
type MockMailer struct{}

func (MockMailer) SendCode(_ context.Context, email, code string) error {
	slog.Info("mock mailer: email login code", "email", email, "code", code)
	return nil
}

// randCode 6 位数字验证码（crypto/rand）。
func randCode() string {
	b := make([]byte, 6)
	_, _ = rand.Read(b)
	digits := make([]byte, 6)
	for i, v := range b {
		digits[i] = '0' + v%10
	}
	return string(digits)
}

// EmailCode POST /v1/auth/email/code 🔓
func (h *Handlers) EmailCode(c *gin.Context) {
	var req protocol.AuthEmailCodeRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, protocol.NewAPIError(protocol.ErrBadRequest))
		return
	}
	email := strings.ToLower(strings.TrimSpace(req.Email))

	// 60s 冷却（SET NX），防轰炸
	ok, err := h.RDB.SetNX(c, store.KeyAuthEmailCd(email), 1, emailCodeCooldown).Result()
	if err != nil {
		c.JSON(http.StatusInternalServerError, protocol.NewAPIError(protocol.ErrInternal))
		return
	}
	if !ok {
		c.JSON(http.StatusTooManyRequests, protocol.NewAPIError(protocol.ErrRateLimited))
		return
	}

	code := randCode()
	// ⚠️ 开发期便利：AUTH_EMAIL_DEV_CODE 显式设置时验证码固定（run-dev.sh 设 888888），
	// 免翻日志。仅开发脚本会设置该变量；生产部署不设即为随机码，启动时若设有醒目告警。
	if h.DevCode != "" {
		code = h.DevCode
	}
	if err := h.RDB.Set(c, store.KeyAuthEmail(email), code, emailCodeTTL).Err(); err != nil {
		c.JSON(http.StatusInternalServerError, protocol.NewAPIError(protocol.ErrInternal))
		return
	}
	if err := h.Mailer.SendCode(c, email, code); err != nil {
		slog.Error("send email code failed", "email", email, "err", err)
		c.JSON(http.StatusInternalServerError, protocol.NewAPIError(protocol.ErrInternal))
		return
	}
	c.Status(http.StatusNoContent)
}

// EmailLogin POST /v1/auth/email 🔓
func (h *Handlers) EmailLogin(c *gin.Context) {
	var req protocol.AuthEmailRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, protocol.NewAPIError(protocol.ErrBadRequest))
		return
	}
	email := strings.ToLower(strings.TrimSpace(req.Email))

	want, err := h.RDB.Get(c, store.KeyAuthEmail(email)).Result()
	if err == redis.Nil || subtle.ConstantTimeCompare([]byte(want), []byte(req.Code)) != 1 {
		c.JSON(http.StatusBadRequest, protocol.NewAPIError(protocol.ErrBadRequest))
		return
	}
	if err != nil && err != redis.Nil {
		c.JSON(http.StatusInternalServerError, protocol.NewAPIError(protocol.ErrInternal))
		return
	}
	// 一次性：校验通过即删，防复用
	h.RDB.Del(c, store.KeyAuthEmail(email))

	user, err := findOrCreateUserByEmail(h.DB, email)
	if err != nil {
		c.JSON(http.StatusInternalServerError, protocol.NewAPIError(protocol.ErrInternal))
		return
	}
	token, err := h.JWT.Sign(user.ID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, protocol.NewAPIError(protocol.ErrInternal))
		return
	}
	c.JSON(http.StatusOK, protocol.AuthTokenResponse{
		Token: token,
		User:  protocol.UserInfo{UserID: user.ID, NicknameMasked: MaskNickname(user.Nickname)},
	})
}

// findOrCreateUserByEmail 邮箱身份建号/登录（昵称取邮箱前缀）。
func findOrCreateUserByEmail(db *gorm.DB, email string) (*store.User, error) {
	var ident store.EmailIdentity
	err := db.Where("email = ?", email).First(&ident).Error
	if err == nil {
		var u store.User
		return &u, db.First(&u, "id = ?", ident.UserID).Error
	}
	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, err
	}

	nick := email
	if i := strings.IndexByte(email, '@'); i > 0 {
		nick = email[:i]
	}
	user := store.User{ID: strings.ReplaceAll(uuid.NewString(), "-", "")[:24], Nickname: nick}
	err = db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Create(&user).Error; err != nil {
			return err
		}
		return tx.Create(&store.EmailIdentity{UserID: user.ID, Email: email}).Error
	})
	if err != nil {
		return nil, err
	}
	return &user, nil
}
