package handlers

import (
	"net/http"
	"strconv"
	"sync"
	"time"

	"sale/internal/models"
	"sale/internal/schemas"
	"sale/internal/utils"

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

type AuthHandler struct{}

func NewAuthHandler() *AuthHandler {
	return &AuthHandler{}
}

var (
	verifyCodes     = make(map[string]verifyCodeEntry)
	verifyCodesMux  sync.Mutex
)

type verifyCodeEntry struct {
	Code      string
	ExpiresAt time.Time
}

func storeVerifyCode(email, code string) {
	verifyCodesMux.Lock()
	defer verifyCodesMux.Unlock()
	verifyCodes[email] = verifyCodeEntry{
		Code:      code,
		ExpiresAt: time.Now().Add(30 * time.Minute),
	}
}

func checkVerifyCode(email, code string) bool {
	verifyCodesMux.Lock()
	defer verifyCodesMux.Unlock()
	entry, ok := verifyCodes[email]
	if !ok {
		return false
	}
	if time.Now().After(entry.ExpiresAt) {
		delete(verifyCodes, email)
		return false
	}
	if entry.Code != code {
		return false
	}
	delete(verifyCodes, email)
	return true
}

func (h *AuthHandler) Register(c *gin.Context) {
	var req schemas.RegisterRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	var existingUser models.User
	if err := utils.DB.Where("email = ?", req.Email).First(&existingUser).Error; err == nil {
		c.JSON(http.StatusConflict, gin.H{"error": "Email already registered"})
		return
	}

	if err := utils.DB.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
		c.JSON(http.StatusConflict, gin.H{"error": "Username already taken"})
		return
	}

	hashedPassword, err := utils.HashPassword(req.Password)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
		return
	}

	user := models.User{
		Username:     req.Username,
		Email:        req.Email,
		PasswordHash: hashedPassword,
		Role:         "user",
		InviteCode:   utils.GenerateInviteCode(),
		IsActive:     true,
	}

	if req.InviteCode != "" {
		var referrer models.User
		if err := utils.DB.Where("invite_code = ?", req.InviteCode).First(&referrer).Error; err == nil {
			user.ReferredBy = &referrer.ID
		}
	}

	if err := utils.DB.Create(&user).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
		return
	}

	if user.ReferredBy != nil {
		utils.DB.Model(&models.User{}).Where("id = ?", *user.ReferredBy).
			UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + 1"))
	}

	verifyCode := utils.GenerateVerifyCode()
	storeVerifyCode(req.Email, verifyCode)
	utils.SendVerifyEmail(req.Email, verifyCode)

	token, _ := utils.GenerateToken(user.ID, user.Role)
	c.JSON(http.StatusCreated, gin.H{
		"token": token,
		"user": schemas.UserResponse{
			ID:              user.ID,
			Username:        user.Username,
			Email:           user.Email,
			Role:            user.Role,
			PurchaseCredits: user.PurchaseCredits,
			InviteCode:      user.InviteCode,
			EmailVerified:   user.EmailVerified,
		},
	})
}

func (h *AuthHandler) Login(c *gin.Context) {
	var req schemas.LoginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	var user models.User
	if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
		return
	}

	if !utils.CheckPassword(req.Password, user.PasswordHash) {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
		return
	}

	if !user.IsActive {
		c.JSON(http.StatusForbidden, gin.H{"error": "Account is disabled"})
		return
	}

	token, _ := utils.GenerateToken(user.ID, user.Role)
	c.JSON(http.StatusOK, gin.H{
		"token": token,
		"user": schemas.UserResponse{
			ID:              user.ID,
			Username:        user.Username,
			Email:           user.Email,
			Role:            user.Role,
			PurchaseCredits: user.PurchaseCredits,
			InviteCode:      user.InviteCode,
			EmailVerified:   user.EmailVerified,
		},
	})
}

func (h *AuthHandler) ForgotPassword(c *gin.Context) {
	var req schemas.ForgotPasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	var user models.User
	if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
		c.JSON(http.StatusOK, gin.H{"message": "If the email exists, a verification code has been sent"})
		return
	}

	code := utils.GenerateVerifyCode()
	storeVerifyCode(req.Email, code)
	utils.SendResetPasswordEmail(req.Email, code)

	c.JSON(http.StatusOK, gin.H{"message": "If the email exists, a verification code has been sent"})
}

func (h *AuthHandler) ResetPassword(c *gin.Context) {
	var req schemas.ResetPasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if !checkVerifyCode(req.Email, req.Code) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid or expired verification code"})
		return
	}

	var user models.User
	if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email"})
		return
	}

	hashedPassword, err := utils.HashPassword(req.Password)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
		return
	}

	utils.DB.Model(&user).Update("password_hash", hashedPassword)
	c.JSON(http.StatusOK, gin.H{"message": "Password reset successfully"})
}

func (h *AuthHandler) VerifyEmail(c *gin.Context) {
	var req schemas.VerifyEmailRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if !checkVerifyCode(req.Email, req.Code) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid or expired verification code"})
		return
	}

	var user models.User
	if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "User not found"})
		return
	}

	utils.DB.Model(&user).Update("email_verified", true)
	c.JSON(http.StatusOK, gin.H{"message": "Email verified successfully"})
}

func (h *AuthHandler) GetProfile(c *gin.Context) {
	userID := c.GetUint("user_id")
	var user models.User
	if err := utils.DB.First(&user, userID).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	c.JSON(http.StatusOK, schemas.UserResponse{
		ID:              user.ID,
		Username:        user.Username,
		Email:           user.Email,
		Role:            user.Role,
		PurchaseCredits: user.PurchaseCredits,
		InviteCode:      user.InviteCode,
		EmailVerified:   user.EmailVerified,
	})
}

func (h *AuthHandler) UpdateProfile(c *gin.Context) {
	userID := c.GetUint("user_id")
	var user models.User
	if err := utils.DB.First(&user, userID).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	var updateData map[string]interface{}
	if err := c.ShouldBindJSON(&updateData); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if username, ok := updateData["username"].(string); ok && username != "" {
		user.Username = username
	}

	utils.DB.Save(&user)
	c.JSON(http.StatusOK, gin.H{"message": "Profile updated successfully"})
}

func (h *AuthHandler) ChangePassword(c *gin.Context) {
	userID := c.GetUint("user_id")
	var req schemas.ChangePasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	var user models.User
	if err := utils.DB.First(&user, userID).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	if !utils.CheckPassword(req.OldPassword, user.PasswordHash) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Old password is incorrect"})
		return
	}

	hashedPassword, _ := utils.HashPassword(req.NewPassword)
	utils.DB.Model(&user).Update("password_hash", hashedPassword)
	c.JSON(http.StatusOK, gin.H{"message": "Password changed successfully"})
}

func (h *AuthHandler) SendVerifyCode(c *gin.Context) {
	userID := c.GetUint("user_id")
	var user models.User
	if err := utils.DB.First(&user, userID).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	if user.EmailVerified {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Email already verified"})
		return
	}

	code := utils.GenerateVerifyCode()
	storeVerifyCode(user.Email, code)
	utils.SendVerifyEmail(user.Email, code)

	c.JSON(http.StatusOK, gin.H{"message": "Verification code sent", "expires_at": time.Now().Add(30 * time.Minute)})
}

func (h *AuthHandler) CheckInstalled(c *gin.Context) {
	var count int64
	utils.DB.Model(&models.User{}).Where("role = ?", "admin").Count(&count)
	c.JSON(http.StatusOK, gin.H{"installed": count > 0})
}

func (h *AuthHandler) Install(c *gin.Context) {
	var count int64
	utils.DB.Model(&models.User{}).Where("role = ?", "admin").Count(&count)
	if count > 0 {
		c.JSON(http.StatusForbidden, gin.H{"error": "System already installed"})
		return
	}

	var req schemas.InstallRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	hashedPassword, err := utils.HashPassword(req.Password)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
		return
	}

	admin := models.User{
		Username:     req.Username,
		Email:        req.Email,
		PasswordHash: hashedPassword,
		Role:         "admin",
		InviteCode:   utils.GenerateInviteCode(),
		IsActive:     true,
	}

	if err := utils.DB.Create(&admin).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create admin user"})
		return
	}

	token, _ := utils.GenerateToken(admin.ID, admin.Role)
	c.JSON(http.StatusCreated, gin.H{
		"token": token,
		"user": schemas.UserResponse{
			ID:              admin.ID,
			Username:        admin.Username,
			Email:           admin.Email,
			Role:            admin.Role,
			PurchaseCredits: admin.PurchaseCredits,
			InviteCode:      admin.InviteCode,
			EmailVerified:   admin.EmailVerified,
		},
	})
}

func (h *AuthHandler) AdminGetUsers(c *gin.Context) {
	var users []models.User
	utils.DB.Select("id, username, email, role, purchase_credits, is_active, email_verified, created_at").Find(&users)
	c.JSON(http.StatusOK, gin.H{"data": users})
}

func (h *AuthHandler) AdminUpdateUser(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var user models.User
	if err := utils.DB.First(&user, id).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	var req struct {
		Username *string `json:"username"`
		Email    *string `json:"email"`
		Role     *string `json:"role"`
		IsActive *bool   `json:"is_active"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	updates := make(map[string]interface{})
	if req.Username != nil {
		updates["username"] = *req.Username
	}
	if req.Email != nil {
		updates["email"] = *req.Email
	}
	if req.Role != nil {
		updates["role"] = *req.Role
	}
	if req.IsActive != nil {
		updates["is_active"] = *req.IsActive
	}

	utils.DB.Model(&user).Updates(updates)
	utils.DB.First(&user, id)
	c.JSON(http.StatusOK, gin.H{"data": user})
}

func (h *AuthHandler) AdminDeleteUser(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var user models.User
	if err := utils.DB.First(&user, id).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}
	if user.Role == "admin" {
		c.JSON(http.StatusForbidden, gin.H{"error": "Cannot delete admin user"})
		return
	}
	utils.DB.Delete(&user)
	c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"})
}
