package handlers

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/crypto/bcrypt"

	"sproutgate-backend/internal/email"
	"sproutgate-backend/internal/models"
	"sproutgate-backend/internal/storage"
)

func (h *Handler) Register(c *gin.Context) {
	var req registerRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	req.Account = storage.NormalizeSelfServiceAccount(req.Account)
	req.Email = strings.TrimSpace(req.Email)
	inviteTrim := strings.TrimSpace(req.InviteCode)
	if req.Account == "" || strings.TrimSpace(req.Password) == "" || req.Email == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "account, password and email are required"})
		return
	}
	if err := h.store.ValidateSelfServiceAccount(req.Account); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	requireInv := h.store.RegistrationRequireInvite()
	if requireInv && inviteTrim == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invite code is required"})
		return
	}
	if inviteTrim != "" {
		if err := h.store.ValidateInviteForRegister(inviteTrim); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
	}
	if _, found, err := h.store.GetUser(req.Account); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load user"})
		return
	} else if found {
		c.JSON(http.StatusBadRequest, gin.H{"error": "account already exists"})
		return
	}

	code, err := generateVerificationCode()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate code"})
		return
	}
	hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash password"})
		return
	}
	expiresAt := time.Now().Add(10 * time.Minute)
	pending := models.PendingUser{
		Account:      req.Account,
		PasswordHash: string(hash),
		Username:     req.Username,
		Email:        req.Email,
		CodeHash:     hashCode(code),
		CreatedAt:    models.NowISO(),
		ExpiresAt:    expiresAt.Format(time.RFC3339),
	}
	if inviteTrim != "" {
		pending.InviteCode = strings.ToUpper(inviteTrim)
	}
	if err := h.store.SavePending(pending); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save pending user"})
		return
	}
	if err := email.SendVerificationEmail(h.store.EmailConfig(), req.Email, code, 10*time.Minute); err != nil {
		_ = h.store.DeletePending(req.Account)
		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to send email: %v", err)})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"sent":      true,
		"expiresAt": expiresAt.Format(time.RFC3339),
	})
}

func (h *Handler) VerifyEmail(c *gin.Context) {
	var req verifyEmailRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	req.Account = storage.NormalizeSelfServiceAccount(req.Account)
	req.Code = strings.TrimSpace(req.Code)
	if req.Account == "" || req.Code == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "account and code are required"})
		return
	}
	if err := h.store.ValidateSelfServiceAccount(req.Account); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	pending, found, err := h.store.GetPending(req.Account)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load pending user"})
		return
	}
	if !found {
		c.JSON(http.StatusBadRequest, gin.H{"error": "pending registration not found"})
		return
	}
	expiresAt, err := time.Parse(time.RFC3339, pending.ExpiresAt)
	if err != nil || time.Now().After(expiresAt) {
		_ = h.store.DeletePending(req.Account)
		c.JSON(http.StatusBadRequest, gin.H{"error": "verification code expired"})
		return
	}
	if !verifyCode(req.Code, pending.CodeHash) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid verification code"})
		return
	}
	sproutCoins := 0
	if strings.TrimSpace(pending.InviteCode) != "" {
		sproutCoins = h.store.RegistrationInviteRegisterRewardCoins()
	}
	record := models.UserRecord{
		Account:         pending.Account,
		PasswordHash:    pending.PasswordHash,
		Username:        pending.Username,
		Email:           pending.Email,
		Level:           0,
		SproutCoins:     sproutCoins,
		SecondaryEmails: []string{},
		CreatedAt:       models.NowISO(),
		UpdatedAt:       models.NowISO(),
	}
	if err := h.store.CreateUser(record); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if strings.TrimSpace(pending.InviteCode) != "" {
		if err := h.store.RedeemInvite(pending.InviteCode); err != nil {
			_ = h.store.DeleteUser(record.Account)
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
	}
	_ = h.store.DeletePending(req.Account)
	c.JSON(http.StatusCreated, gin.H{"created": true, "user": record.OwnerPublic()})
}

func (h *Handler) ForgotPassword(c *gin.Context) {
	var req forgotPasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	req.Account = strings.TrimSpace(req.Account)
	req.Email = strings.TrimSpace(req.Email)
	if req.Account == "" || req.Email == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "account and email are required"})
		return
	}
	user, found, err := h.store.GetUser(req.Account)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load user"})
		return
	}
	if !found || strings.TrimSpace(user.Email) == "" || user.Email != req.Email {
		c.JSON(http.StatusBadRequest, gin.H{"error": "account or email not matched"})
		return
	}
	if user.Banned {
		writeBanJSON(c, user.BanReason)
		return
	}
	code, err := generateVerificationCode()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate code"})
		return
	}
	expiresAt := time.Now().Add(10 * time.Minute)
	resetRecord := models.ResetPassword{
		Account:   user.Account,
		Email:     user.Email,
		CodeHash:  hashCode(code),
		CreatedAt: models.NowISO(),
		ExpiresAt: expiresAt.Format(time.RFC3339),
	}
	if err := h.store.SaveReset(resetRecord); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save reset token"})
		return
	}
	if err := email.SendResetPasswordEmail(h.store.EmailConfig(), user.Email, code, 10*time.Minute); err != nil {
		_ = h.store.DeleteReset(user.Account)
		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to send email: %v", err)})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"sent":      true,
		"expiresAt": expiresAt.Format(time.RFC3339),
	})
}

func (h *Handler) ResetPassword(c *gin.Context) {
	var req resetPasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}
	req.Account = strings.TrimSpace(req.Account)
	req.Code = strings.TrimSpace(req.Code)
	if req.Account == "" || req.Code == "" || strings.TrimSpace(req.NewPassword) == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "account, code and newPassword are required"})
		return
	}
	resetRecord, found, err := h.store.GetReset(req.Account)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load reset token"})
		return
	}
	if !found {
		c.JSON(http.StatusBadRequest, gin.H{"error": "reset request not found"})
		return
	}
	expiresAt, err := time.Parse(time.RFC3339, resetRecord.ExpiresAt)
	if err != nil || time.Now().After(expiresAt) {
		_ = h.store.DeleteReset(req.Account)
		c.JSON(http.StatusBadRequest, gin.H{"error": "reset code expired"})
		return
	}
	if !verifyCode(req.Code, resetRecord.CodeHash) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid reset code"})
		return
	}
	user, found, err := h.store.GetUser(req.Account)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load user"})
		return
	}
	if !found {
		c.JSON(http.StatusBadRequest, gin.H{"error": "user not found"})
		return
	}
	if user.Banned {
		writeBanJSON(c, user.BanReason)
		return
	}
	hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash password"})
		return
	}
	user.PasswordHash = string(hash)
	if err := h.store.SaveUser(user); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save user"})
		return
	}
	_ = h.store.DeleteReset(req.Account)
	c.JSON(http.StatusOK, gin.H{"reset": true})
}
