package handler

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"net/http"
	"strings"
	"time"
	"xboard-go/internal/database"
	"xboard-go/internal/model"
	"xboard-go/internal/service"
	"xboard-go/pkg/utils"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

type LoginRequest struct {
	Email    string `json:"email" binding:"required,email"`
	Password string `json:"password" binding:"required"`
}

type RegisterRequest struct {
	Email      string  `json:"email" binding:"required,email"`
	Password   string  `json:"password" binding:"required,min=8"`
	InviteCode *string `json:"invite_code"`
}

func Login(c *gin.Context) {
	var req LoginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, http.StatusBadRequest, "invalid request body")
		return
	}

	var user model.User
	if err := database.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
		Fail(c, http.StatusUnauthorized, "email or password is incorrect")
		return
	}

	if !utils.CheckPassword(req.Password, user.Password, user.PasswordAlgo, user.PasswordSalt) {
		Fail(c, http.StatusUnauthorized, "email or password is incorrect")
		return
	}

	token, err := utils.GenerateToken(user.ID, user.IsAdmin)
	if err != nil {
		Fail(c, http.StatusInternalServerError, "failed to create auth token")
		return
	}

	now := time.Now().Unix()
	_ = database.DB.Model(&model.User{}).Where("id = ?", user.ID).Update("last_login_at", now).Error
	service.TrackSession(user.ID, token, c.ClientIP(), c.GetHeader("User-Agent"))

	Success(c, gin.H{
		"token":     token,
		"auth_data": token,
		"is_admin":  user.IsAdmin,
	})
}

func Register(c *gin.Context) {
	var req RegisterRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, http.StatusBadRequest, "invalid request body")
		return
	}

	var count int64
	database.DB.Model(&model.User{}).Where("email = ?", req.Email).Count(&count)
	if count > 0 {
		Fail(c, http.StatusBadRequest, "email already exists")
		return
	}

	hashedPassword, err := utils.HashPassword(req.Password)
	if err != nil {
		Fail(c, http.StatusInternalServerError, "failed to hash password")
		return
	}

	now := time.Now().Unix()
	user := model.User{
		Email:     req.Email,
		Password:  hashedPassword,
		UUID:      uuid.New().String(),
		Token:     service.GenerateSubscriptionToken(),
		CreatedAt: now,
		UpdatedAt: now,
	}

	if err := database.DB.Create(&user).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "register failed")
		return
	}

	token, err := utils.GenerateToken(user.ID, user.IsAdmin)
	if err != nil {
		Fail(c, http.StatusInternalServerError, "failed to create auth token")
		return
	}
	service.TrackSession(user.ID, token, c.ClientIP(), c.GetHeader("User-Agent"))

	Success(c, gin.H{
		"token":     token,
		"auth_data": token,
		"is_admin":  user.IsAdmin,
	})
}

func Token2Login(c *gin.Context) {
	verify := strings.TrimSpace(c.Query("verify"))
	if verify == "" {
		Fail(c, http.StatusBadRequest, "verify token is required")
		return
	}

	userID, ok := service.ResolveQuickLoginToken(verify)
	if !ok {
		Fail(c, http.StatusUnauthorized, "verify token is invalid or expired")
		return
	}

	var user model.User
	if err := database.DB.First(&user, userID).Error; err != nil {
		Fail(c, http.StatusNotFound, "user not found")
		return
	}

	token, err := utils.GenerateToken(user.ID, user.IsAdmin)
	if err != nil {
		Fail(c, http.StatusInternalServerError, "failed to create auth token")
		return
	}
	service.TrackSession(user.ID, token, c.ClientIP(), c.GetHeader("User-Agent"))
	_ = database.DB.Model(&model.User{}).Where("id = ?", user.ID).Update("last_login_at", time.Now().Unix()).Error

	Success(c, gin.H{
		"token":     token,
		"auth_data": token,
		"is_admin":  user.IsAdmin,
	})
}

func SendEmailVerify(c *gin.Context) {
	var req struct {
		Email string `json:"email" binding:"required,email"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, http.StatusBadRequest, "invalid email")
		return
	}

	code, err := randomDigits(6)
	if err != nil {
		Fail(c, http.StatusInternalServerError, "failed to generate verify code")
		return
	}

	email := strings.ToLower(strings.TrimSpace(req.Email))
	subject := fmt.Sprintf("[%s] Email verification code", service.MustGetString("app_name", "XBoard"))
	textBody := strings.Join([]string{
		"Your email verification code is:",
		code,
		"",
		"This code will expire in 10 minutes.",
	}, "\n")
	htmlBody := fmt.Sprintf(
		"<h2>Email verification</h2><p>Your verification code is <strong style=\"font-size:24px;letter-spacing:4px;\">%s</strong>.</p><p>This code will expire in 10 minutes.</p>",
		code,
	)
	if err := service.SendMailWithCurrentSettings(service.EmailMessage{
		To:       []string{email},
		Subject:  subject,
		TextBody: textBody,
		HTMLBody: htmlBody,
	}); err != nil {
		Fail(c, http.StatusInternalServerError, "failed to send verification email: "+err.Error())
		return
	}

	service.StoreEmailVerifyCode(email, code, 10*time.Minute)
	SuccessMessage(c, "email verify code generated", gin.H{
		"email":      req.Email,
		"debug_code": code,
		"expires_in": 600,
	})
}

func ForgetPassword(c *gin.Context) {
	var req struct {
		Email     string `json:"email" binding:"required,email"`
		EmailCode string `json:"email_code" binding:"required"`
		Password  string `json:"password" binding:"required,min=8"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		Fail(c, http.StatusBadRequest, "invalid request body")
		return
	}

	email := strings.ToLower(strings.TrimSpace(req.Email))
	if !service.CheckEmailVerifyCode(email, strings.TrimSpace(req.EmailCode)) {
		Fail(c, http.StatusBadRequest, "email verify code is invalid")
		return
	}

	hashed, err := utils.HashPassword(req.Password)
	if err != nil {
		Fail(c, http.StatusInternalServerError, "failed to hash password")
		return
	}

	updates := map[string]any{
		"password":      hashed,
		"password_algo": nil,
		"password_salt": nil,
		"updated_at":    time.Now().Unix(),
	}
	if err := database.DB.Model(&model.User{}).Where("email = ?", email).Updates(updates).Error; err != nil {
		Fail(c, http.StatusInternalServerError, "password reset failed")
		return
	}

	SuccessMessage(c, "password reset success", true)
}

func randomDigits(length int) (string, error) {
	if length <= 0 {
		return "", nil
	}

	buf := make([]byte, length)
	if _, err := rand.Read(buf); err != nil {
		return "", err
	}

	encoded := hex.EncodeToString(buf)
	digits := make([]byte, 0, length)
	for i := 0; i < len(encoded) && len(digits) < length; i++ {
		digits = append(digits, '0'+(encoded[i]%10))
	}
	return string(digits), nil
}
