package handlers

import (
	"crypto/rand"
	"math/big"
	"net/http"
	"strconv"
	"time"

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

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

type LotteryHandler struct{}

func NewLotteryHandler() *LotteryHandler {
	return &LotteryHandler{}
}

func (h *LotteryHandler) List(c *gin.Context) {
	var lotteries []models.Lottery
	utils.DB.Where("is_active = ?", true).Preload("Prizes").Order("created_at DESC").Find(&lotteries)
	c.JSON(http.StatusOK, gin.H{"data": lotteries})
}

func (h *LotteryHandler) GetByID(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var lottery models.Lottery
	if err := utils.DB.Preload("Prizes").First(&lottery, id).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": lottery})
}

func (h *LotteryHandler) Register(c *gin.Context) {
	userID := c.GetUint("user_id")
	id, _ := strconv.Atoi(c.Param("id"))

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

	now := time.Now()
	if now.Before(lottery.StartTime) || now.After(lottery.EndTime) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Lottery is not in registration period"})
		return
	}

	if !lottery.IsActive {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Lottery is not active"})
		return
	}

	if lottery.TotalQuota != nil && *lottery.TotalQuota > 0 {
		var participantCount int64
		utils.DB.Model(&models.LotteryParticipant{}).Where("lottery_id = ?", id).Count(&participantCount)
		if int(participantCount) >= *lottery.TotalQuota {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Lottery quota is full"})
			return
		}
	}

	if lottery.DailyQuota != nil && *lottery.DailyQuota > 0 {
		today := time.Now().Truncate(24 * time.Hour)
		tomorrow := today.Add(24 * time.Hour)
		var dailyCount int64
		utils.DB.Model(&models.LotteryParticipant{}).Where("lottery_id = ? AND registered_at >= ? AND registered_at < ?", id, today, tomorrow).Count(&dailyCount)
		if int(dailyCount) >= *lottery.DailyQuota {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Daily quota is full"})
			return
		}
	}

	var existing models.LotteryParticipant
	if err := utils.DB.Where("lottery_id = ? AND user_id = ?", id, userID).First(&existing).Error; err == nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Already registered"})
		return
	}

	var totalSpent float64
	utils.DB.Model(&models.Order{}).
		Where("user_id = ? AND status = ?", userID, models.OrderStatusCompleted).
		Select("COALESCE(SUM(total_amount), 0)").
		Scan(&totalSpent)

	participant := models.LotteryParticipant{
		LotteryID:      lottery.ID,
		UserID:         userID,
		PurchaseWeight: int(totalSpent),
	}

	utils.DB.Create(&participant)
	c.JSON(http.StatusOK, gin.H{"message": "Registered successfully"})
}

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

	startTime, _ := time.Parse(time.RFC3339, req.StartTime)
	endTime, _ := time.Parse(time.RFC3339, req.EndTime)

	lottery := models.Lottery{
		Name:                 req.Name,
		Description:          req.Description,
		StartTime:            startTime,
		EndTime:              endTime,
		Cycle:                req.Cycle,
		DailyQuota:           req.DailyQuota,
		TotalQuota:           req.TotalQuota,
		RegistrationValidity: req.RegistrationValidity,
		IsActive:             true,
	}

	utils.DB.Create(&lottery)
	c.JSON(http.StatusCreated, gin.H{"data": lottery})
}

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

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

	updates := make(map[string]interface{})
	if req.Name != nil {
		updates["name"] = *req.Name
	}
	if req.Description != nil {
		updates["description"] = *req.Description
	}
	if req.StartTime != nil {
		t, _ := time.Parse(time.RFC3339, *req.StartTime)
		updates["start_time"] = t
	}
	if req.EndTime != nil {
		t, _ := time.Parse(time.RFC3339, *req.EndTime)
		updates["end_time"] = t
	}
	if req.Cycle != nil {
		updates["cycle"] = *req.Cycle
	}
	if req.DailyQuota != nil {
		updates["daily_quota"] = *req.DailyQuota
	}
	if req.TotalQuota != nil {
		updates["total_quota"] = *req.TotalQuota
	}
	if req.RegistrationValidity != nil {
		updates["registration_validity"] = *req.RegistrationValidity
	}
	if req.IsActive != nil {
		updates["is_active"] = *req.IsActive
	}

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

func (h *LotteryHandler) Delete(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	utils.DB.Delete(&models.Lottery{}, id)
	c.JSON(http.StatusOK, gin.H{"message": "Lottery deleted successfully"})
}

func (h *LotteryHandler) AddPrize(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))
	var req schemas.AddLotteryPrizeRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	prize := models.LotteryPrize{
		LotteryID:    uint(id),
		Name:         req.Name,
		Type:         req.Type,
		Quantity:     req.Quantity,
		Weight:       req.Weight,
		CreditReward: req.CreditReward,
		DrawMode:     req.DrawMode,
	}

	utils.DB.Create(&prize)
	c.JSON(http.StatusCreated, gin.H{"data": prize})
}

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

	var participants []models.LotteryParticipant
	utils.DB.Where("lottery_id = ?", id).Find(&participants)

	if len(participants) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "No participants"})
		return
	}

	var winners []models.LotteryWinner

	for _, prize := range lottery.Prizes {
		remaining := prize.Quantity

		var weightParticipants []models.LotteryParticipant
		var randomParticipants []models.LotteryParticipant

		if prize.DrawMode == models.DrawModeWeight {
			weightParticipants = participants
		} else {
			randomParticipants = participants
		}

		selectedParticipants := weightParticipants
		if len(selectedParticipants) == 0 {
			selectedParticipants = randomParticipants
		}

		totalWeight := 0
		for _, p := range selectedParticipants {
			w := p.PurchaseWeight
			if w < 1 {
				w = 1
			}
			totalWeight += w
		}

		selected := make(map[uint]bool)
		for remaining > 0 && len(selected) < len(selectedParticipants) {
			r, err := rand.Int(rand.Reader, big.NewInt(int64(totalWeight)))
			if err != nil {
				break
			}
			cumWeight := 0
			for _, p := range selectedParticipants {
				if selected[p.UserID] {
					continue
				}
				w := p.PurchaseWeight
				if w < 1 {
					w = 1
				}
				cumWeight += w
				if cumWeight > int(r.Int64()) {
					winner := models.LotteryWinner{
						LotteryID: lottery.ID,
						PrizeID:   prize.ID,
						UserID:    p.UserID,
						DrawnAt:   time.Now(),
					}
					winners = append(winners, winner)
					selected[p.UserID] = true
					remaining--

					if prize.Type == models.PrizeTypeCredit && prize.CreditReward != nil {
						utils.DB.Model(&models.User{}).Where("id = ?", p.UserID).
							UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", *prize.CreditReward))
					}
					break
				}
			}
		}
	}

	if len(winners) > 0 {
		utils.DB.Create(&winners)
	}

	c.JSON(http.StatusOK, gin.H{"data": winners, "message": "Draw completed successfully"})
}
