package handlers

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

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

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

type CartHandler struct{}

func NewCartHandler() *CartHandler {
	return &CartHandler{}
}

func (h *CartHandler) List(c *gin.Context) {
	userID := c.GetUint("user_id")
	var carts []models.Cart
	utils.DB.Where("user_id = ?", userID).Preload("Product").Find(&carts)
	c.JSON(http.StatusOK, gin.H{"data": carts})
}

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

	var cart models.Cart
	result := utils.DB.Where("user_id = ? AND product_id = ?", userID, req.ProductID).First(&cart)
	if result.Error == nil {
		cart.Quantity += req.Quantity
		utils.DB.Save(&cart)
	} else {
		cart = models.Cart{
			UserID:    userID,
			ProductID: req.ProductID,
			Quantity:  req.Quantity,
		}
		utils.DB.Create(&cart)
	}

	utils.DB.Preload("Product").First(&cart, cart.ID)
	c.JSON(http.StatusOK, gin.H{"data": cart})
}

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

	var cart models.Cart
	if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&cart).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Cart item not found"})
		return
	}

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

	cart.Quantity = req.Quantity
	utils.DB.Save(&cart)
	utils.DB.Preload("Product").First(&cart, cart.ID)
	c.JSON(http.StatusOK, gin.H{"data": cart})
}

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

	if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&models.Cart{}).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete cart item"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Cart item deleted successfully"})
}

type OrderHandler struct{}

func NewOrderHandler() *OrderHandler {
	return &OrderHandler{}
}

func checkOrderExpired(order *models.Order) {
	if order.Status != models.OrderStatusPendingPayment || order.PaymentExpiresAt == nil {
		return
	}
	if time.Now().After(*order.PaymentExpiresAt) {
		tx := utils.DB.Begin()
		tx.Model(order).Update("status", models.OrderStatusCancelled)
		for _, item := range order.OrderItems {
			var inventory models.Inventory
			if err := tx.Where("product_id = ?", item.ProductID).First(&inventory).Error; err == nil {
				tx.Model(&inventory).UpdateColumn("quantity", inventory.Quantity+item.Quantity)
			}
			var product models.Product
			if err := tx.First(&product, item.ProductID).Error; err == nil {
				if product.RequireCredit {
					tx.Model(&models.User{}).Where("id = ?", order.UserID).
						UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", product.CreditCost*item.Quantity))
				}
				if product.CreditReward > 0 {
					tx.Model(&models.User{}).Where("id = ?", order.UserID).
						UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", product.CreditReward*item.Quantity))
				}
			}
		}
		tx.Commit()
		order.Status = models.OrderStatusCancelled
	}
}

func checkOrdersExpired(orders []models.Order) {
	for i := range orders {
		checkOrderExpired(&orders[i])
	}
}

func (h *OrderHandler) List(c *gin.Context) {
	userID := c.GetUint("user_id")
	role, _ := c.Get("role")

	var orders []models.Order
	query := utils.DB.Model(&models.Order{})

	if role == "user" {
		query = query.Where("user_id = ?", userID)
	} else if role == "supplier" {
		query = query.Where("supplier_id = ?", userID)
	}

	query.Preload("OrderItems.Product").Preload("ShippingAddress").
		Order("created_at DESC").Find(&orders)

	checkOrdersExpired(orders)

	c.JSON(http.StatusOK, gin.H{"data": orders})
}

func (h *OrderHandler) GetByID(c *gin.Context) {
	userID := c.GetUint("user_id")
	role, _ := c.Get("role")
	id, _ := strconv.Atoi(c.Param("id"))

	var order models.Order
	query := utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User")

	if role == "user" {
		query = query.Where("user_id = ?", userID)
	} else if role == "supplier" {
		query = query.Where("supplier_id = ?", userID)
	}

	if err := query.First(&order, id).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
		return
	}

	checkOrderExpired(&order)

	c.JSON(http.StatusOK, gin.H{"data": order})
}

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

	var carts []models.Cart
	cartQuery := utils.DB.Where("user_id = ?", userID).Preload("Product")
	if len(req.CartItemIDs) > 0 {
		cartQuery = cartQuery.Where("id IN ?", req.CartItemIDs)
	}
	cartQuery.Find(&carts)

	if len(carts) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Cart is empty"})
		return
	}

	var subtotal float64
	var totalQuantity int
	var orderItems []models.OrderItem
	supplierMap := make(map[uint]bool)

	for _, cart := range carts {
		if !cart.Product.IsActive {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Product " + cart.Product.Name + " is no longer available"})
			return
		}

		if cart.Product.MinPurchase > 0 && cart.Quantity < cart.Product.MinPurchase {
			c.JSON(http.StatusBadRequest, gin.H{"error": cart.Product.Name + " minimum purchase is " + strconv.Itoa(cart.Product.MinPurchase)})
			return
		}
		if cart.Product.MaxPurchase != nil && *cart.Product.MaxPurchase > 0 && cart.Quantity > *cart.Product.MaxPurchase {
			c.JSON(http.StatusBadRequest, gin.H{"error": cart.Product.Name + " maximum purchase is " + strconv.Itoa(*cart.Product.MaxPurchase)})
			return
		}

		if cart.Product.RequireCredit {
			var user models.User
			utils.DB.First(&user, userID)
			if user.PurchaseCredits < cart.Product.CreditCost*cart.Quantity {
				c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient purchase credits for " + cart.Product.Name})
				return
			}
		}

		var inventory models.Inventory
		if err := utils.DB.Where("product_id = ?", cart.ProductID).First(&inventory).Error; err == nil {
			if inventory.Quantity < cart.Quantity {
				c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient stock for " + cart.Product.Name})
				return
			}
		}

		subtotal += cart.Product.Price * float64(cart.Quantity)
		totalQuantity += cart.Quantity
		orderItems = append(orderItems, models.OrderItem{
			ProductID: cart.ProductID,
			Quantity:  cart.Quantity,
			Price:     cart.Product.Price,
		})

		if inventory.SupplierID != 0 {
			supplierMap[inventory.SupplierID] = true
		}
	}

	var supplierID *uint
	for sid := range supplierMap {
		sid := sid
		supplierID = &sid
		break
	}

	var shippingFeeFirstWeight, shippingFeePerGram, serviceFeeRate, taxRate float64
	var setting models.SystemSetting
	if err := utils.DB.Where("`key` = ?", "shipping_fee_first_weight").First(&setting).Error; err == nil {
		shippingFeeFirstWeight, _ = strconv.ParseFloat(setting.Value, 64)
	}
	if err := utils.DB.Where("`key` = ?", "shipping_fee_per_gram").First(&setting).Error; err == nil {
		shippingFeePerGram, _ = strconv.ParseFloat(setting.Value, 64)
	}
	if err := utils.DB.Where("`key` = ?", "service_fee_rate").First(&setting).Error; err == nil {
		serviceFeeRate, _ = strconv.ParseFloat(setting.Value, 64)
	}
	if err := utils.DB.Where("`key` = ?", "tax_rate").First(&setting).Error; err == nil {
		taxRate, _ = strconv.ParseFloat(setting.Value, 64)
	}

	shippingFee := shippingFeeFirstWeight
	if subtotal >= 99 || shippingFeeFirstWeight == 0 {
		shippingFee = 0
	} else if totalQuantity > 500 {
		shippingFee += shippingFeePerGram * float64(totalQuantity-500)
	}

	serviceFee := subtotal * serviceFeeRate / 100
	tax := subtotal * taxRate / 100
	totalAmount := subtotal + shippingFee + serviceFee + tax

	order := models.Order{
		UserID:            userID,
		SupplierID:        supplierID,
		Subtotal:          subtotal,
		ShippingFee:       shippingFee,
		ServiceFee:        serviceFee,
		Tax:               tax,
		TotalAmount:       totalAmount,
		Status:            models.OrderStatusPendingPayment,
		ShippingAddressID: &req.ShippingAddressID,
		PaymentMethod:     req.PaymentMethod,
	}

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

	for i := range orderItems {
		orderItems[i].OrderID = order.ID
	}
	if err := tx.Create(&orderItems).Error; err != nil {
		tx.Rollback()
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create order items"})
		return
	}

	for _, cart := range carts {
		if cart.Product.RequireCredit {
			if err := tx.Model(&models.User{}).Where("id = ?", userID).
				UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", cart.Product.CreditCost*cart.Quantity)).Error; err != nil {
				tx.Rollback()
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to deduct credits"})
				return
			}
		}
		if cart.Product.CreditReward > 0 {
			tx.Model(&models.User{}).Where("id = ?", userID).
				UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", cart.Product.CreditReward*cart.Quantity))
		}

		var inventory models.Inventory
		if err := tx.Where("product_id = ?", cart.ProductID).First(&inventory).Error; err == nil {
			if inventory.Quantity >= cart.Quantity {
				tx.Model(&inventory).UpdateColumn("quantity", inventory.Quantity-cart.Quantity)
			}
		}
	}

	cartIDs := make([]uint, len(carts))
	for i, cart := range carts {
		cartIDs[i] = cart.ID
	}
	if err := tx.Where("id IN ? AND user_id = ?", cartIDs, userID).Delete(&models.Cart{}).Error; err != nil {
		tx.Rollback()
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear cart"})
		return
	}

	tx.Commit()

	utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").First(&order, order.ID)
	c.JSON(http.StatusCreated, gin.H{"data": order})
}

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

	var order models.Order
	if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&order).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
		return
	}

	if order.Status == models.OrderStatusShipped || order.Status == models.OrderStatusCompleted {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot refund a shipped or completed order"})
		return
	}

	if order.RefundStatus == models.RefundStatusPending {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Refund already requested"})
		return
	}

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

	utils.DB.Model(&order).Updates(map[string]interface{}{
		"refund_status": models.RefundStatusPending,
		"refund_reason": req.Reason,
		"status":        models.OrderStatusRefunding,
	})

	c.JSON(http.StatusOK, gin.H{"message": "Refund request submitted successfully"})
}

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

	var order models.Order
	if err := utils.DB.Where("id = ? AND supplier_id = ?", id, userID).First(&order).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
		return
	}

	if order.Status != models.OrderStatusPendingConfirm {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be confirmed"})
		return
	}

	utils.DB.Model(&order).Update("status", models.OrderStatusPendingShip)
	c.JSON(http.StatusOK, gin.H{"message": "Order confirmed successfully"})
}

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

	var order models.Order
	if err := utils.DB.Where("id = ? AND supplier_id = ?", id, userID).First(&order).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
		return
	}

	if order.Status != models.OrderStatusPendingShip {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be shipped"})
		return
	}

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

	utils.DB.Model(&order).Updates(map[string]interface{}{
		"status":          models.OrderStatusShipped,
		"tracking_number": req.TrackingNumber,
		"shipping_photo":  req.ShippingPhoto,
		"express_photo":   req.ExpressPhoto,
		"customs_photo":   req.CustomsPhoto,
	})

	c.JSON(http.StatusOK, gin.H{"message": "Order shipped successfully"})
}

func (h *OrderHandler) AdminList(c *gin.Context) {
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))

	var total int64
	utils.DB.Model(&models.Order{}).Count(&total)

	var orders []models.Order
	offset := (page - 1) * pageSize
	utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User").
		Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&orders)

	checkOrdersExpired(orders)

	totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
	c.JSON(http.StatusOK, gin.H{
		"data": orders,
		"pagination": gin.H{
			"page":       page,
			"page_size":  pageSize,
			"total":      total,
			"total_pages": totalPages,
		},
	})
}

func (h *OrderHandler) ProcessRefund(c *gin.Context) {
	id, _ := strconv.Atoi(c.Param("id"))

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

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

	if req.Status == "approved" {
		var setting models.SystemSetting
		feeRate := 0.0
		if err := utils.DB.Where("`key` = ?", "payment_channel_fee_rate").First(&setting).Error; err == nil {
			feeRate, _ = strconv.ParseFloat(setting.Value, 64)
		}

		refundAmount := order.TotalAmount * (1 - feeRate/100)

		tx := utils.DB.Begin()

		tx.Model(&order).Updates(map[string]interface{}{
			"refund_status": models.RefundStatusCompleted,
			"refund_amount": refundAmount,
			"status":        models.OrderStatusRefunded,
		})

		for _, item := range order.OrderItems {
			var product models.Product
			if err := tx.First(&product, item.ProductID).Error; err == nil {
				if product.RequireCredit {
					tx.Model(&models.User{}).Where("id = ?", order.UserID).
						UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", product.CreditCost*item.Quantity))
				}
				if product.CreditReward > 0 {
					tx.Model(&models.User{}).Where("id = ?", order.UserID).
						UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", product.CreditReward*item.Quantity))
				}
			}

			var inventory models.Inventory
			if err := tx.Where("product_id = ?", item.ProductID).First(&inventory).Error; err == nil {
				tx.Model(&inventory).UpdateColumn("quantity", inventory.Quantity+item.Quantity)
			}
		}

		tx.Commit()
	} else {
		utils.DB.Model(&order).Updates(map[string]interface{}{
			"refund_status": models.RefundStatusRejected,
			"status":        models.OrderStatusPendingConfirm,
		})
	}

	c.JSON(http.StatusOK, gin.H{"message": "Refund processed successfully"})
}

func (h *OrderHandler) Export(c *gin.Context) {
	var orders []models.Order
	utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User").
		Order("created_at DESC").Find(&orders)

	c.JSON(http.StatusOK, gin.H{"data": orders})
}

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

	var order models.Order
	if err := utils.DB.Preload("OrderItems").Where("id = ? AND user_id = ?", id, userID).First(&order).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
		return
	}

	if order.Status != models.OrderStatusPendingPayment && order.Status != models.OrderStatusPendingConfirm {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be cancelled"})
		return
	}

	tx := utils.DB.Begin()
	tx.Model(&order).Update("status", models.OrderStatusCancelled)

	for _, item := range order.OrderItems {
		var product models.Product
		if err := tx.First(&product, item.ProductID).Error; err == nil {
			if product.RequireCredit {
				tx.Model(&models.User{}).Where("id = ?", order.UserID).
					UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", product.CreditCost*item.Quantity))
			}
			if product.CreditReward > 0 {
				tx.Model(&models.User{}).Where("id = ?", order.UserID).
					UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", product.CreditReward*item.Quantity))
			}
		}

		var inventory models.Inventory
		if err := tx.Where("product_id = ?", item.ProductID).First(&inventory).Error; err == nil {
			tx.Model(&inventory).UpdateColumn("quantity", inventory.Quantity+item.Quantity)
		}
	}

	tx.Commit()
	c.JSON(http.StatusOK, gin.H{"message": "Order cancelled successfully"})
}

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

	var order models.Order
	if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&order).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
		return
	}

	if order.Status != models.OrderStatusShipped {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be confirmed"})
		return
	}

	utils.DB.Model(&order).Update("status", models.OrderStatusCompleted)
	c.JSON(http.StatusOK, gin.H{"message": "Order confirmed successfully"})
}
