package processor

import (
	"apskel-pos-be/internal/constants"
	"context"
	"errors"
	"fmt"

	"apskel-pos-be/internal/entities"
	"apskel-pos-be/internal/mappers"
	"apskel-pos-be/internal/models"
	"apskel-pos-be/internal/repository"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

type OrderProcessor interface {
	CreateOrder(ctx context.Context, req *models.CreateOrderRequest, organizationID uuid.UUID) (*models.OrderResponse, error)
	AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error)
	UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error)
	GetOrderByID(ctx context.Context, id uuid.UUID) (*models.OrderResponse, error)
	ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error)
	VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID) error
	RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID) error
	CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error)
	RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error
	SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error)
	SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error)
}

type OrderRepository interface {
	Create(ctx context.Context, order *entities.Order) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error)
	GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Order, error)
	Update(ctx context.Context, order *entities.Order) error
	UpdateStatusSuccess(ctx context.Context, id uuid.UUID, orderStatus entities.OrderStatus, paymentStatus entities.PaymentStatus) error
	Delete(ctx context.Context, id uuid.UUID) error
	List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, error)
	GetByOrderNumber(ctx context.Context, orderNumber string) (*entities.Order, error)
	ExistsByOrderNumber(ctx context.Context, orderNumber string) (bool, error)
	VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID) error
	VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID) error
	RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID) error
	UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus) error
	UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus) error
	GetNextOrderNumber(ctx context.Context, organizationID, outletID uuid.UUID) (string, error)
}

type OrderItemRepository interface {
	Create(ctx context.Context, orderItem *entities.OrderItem) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.OrderItem, error)
	GetByOrderID(ctx context.Context, orderID uuid.UUID) ([]*entities.OrderItem, error)
	Update(ctx context.Context, orderItem *entities.OrderItem) error
	Delete(ctx context.Context, id uuid.UUID) error
	RefundOrderItem(ctx context.Context, id uuid.UUID, refundQuantity int, refundAmount float64, reason string, refundedBy uuid.UUID) error
	VoidOrderItem(ctx context.Context, id uuid.UUID, voidQuantity int, reason string, voidedBy uuid.UUID) error
	UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderItemStatus) error
}

type PaymentRepository interface {
	Create(ctx context.Context, payment *entities.Payment) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.Payment, error)
	GetByOrderID(ctx context.Context, orderID uuid.UUID) ([]*entities.Payment, error)
	Update(ctx context.Context, payment *entities.Payment) error
	Delete(ctx context.Context, id uuid.UUID) error
	RefundPayment(ctx context.Context, id uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error
	UpdateStatus(ctx context.Context, id uuid.UUID, status entities.PaymentTransactionStatus) error
	GetTotalPaidByOrderID(ctx context.Context, orderID uuid.UUID) (float64, error)
}

type PaymentOrderItemRepository interface {
	Create(ctx context.Context, paymentOrderItem *entities.PaymentOrderItem) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.PaymentOrderItem, error)
	GetByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]*entities.PaymentOrderItem, error)
	GetPaidQuantitiesByOrderID(ctx context.Context, orderID uuid.UUID) (map[uuid.UUID]int, error)
	Update(ctx context.Context, paymentOrderItem *entities.PaymentOrderItem) error
	Delete(ctx context.Context, id uuid.UUID) error
}

type PaymentMethodRepository interface {
	GetByID(ctx context.Context, id uuid.UUID) (*entities.PaymentMethod, error)
}

type CustomerRepository interface {
	GetByIDAndOrganization(ctx context.Context, id, organizationID uuid.UUID) (*entities.Customer, error)
}

type InventoryMovementService interface {
	CreateIngredientMovement(ctx context.Context, ingredientID, organizationID, outletID, userID uuid.UUID, movementType entities.InventoryMovementType, quantity float64, unitCost float64, reason string, referenceType *entities.InventoryMovementReferenceType, referenceID *uuid.UUID) error
	CreateProductMovement(ctx context.Context, productID, organizationID, outletID, userID uuid.UUID, movementType entities.InventoryMovementType, quantity float64, unitCost float64, reason string, referenceType *entities.InventoryMovementReferenceType, referenceID *uuid.UUID) error
}

type OrderProcessorImpl struct {
	orderRepo                OrderRepository
	orderItemRepo            OrderItemRepository
	paymentRepo              PaymentRepository
	paymentOrderItemRepo     PaymentOrderItemRepository
	productRepo              ProductRepository
	paymentMethodRepo        PaymentMethodRepository
	inventoryRepo            repository.InventoryRepository
	inventoryMovementRepo    repository.InventoryMovementRepository
	productVariantRepo       repository.ProductVariantRepository
	outletRepo               OutletRepository
	customerRepo             CustomerRepository
	splitBillProcessor       SplitBillProcessor
	txManager                *repository.TxManager
	productRecipeRepo        *repository.ProductRecipeRepository
	ingredientRepo           IngredientRepository
	inventoryMovementService InventoryMovementService
}

func NewOrderProcessorImpl(
	orderRepo OrderRepository,
	orderItemRepo OrderItemRepository,
	paymentRepo PaymentRepository,
	paymentOrderItemRepo PaymentOrderItemRepository,
	productRepo ProductRepository,
	paymentMethodRepo PaymentMethodRepository,
	inventoryRepo repository.InventoryRepository,
	inventoryMovementRepo repository.InventoryMovementRepository,
	productVariantRepo repository.ProductVariantRepository,
	outletRepo OutletRepository,
	customerRepo CustomerRepository,
	txManager *repository.TxManager,
	productRecipeRepo *repository.ProductRecipeRepository,
	ingredientRepo IngredientRepository,
	inventoryMovementService InventoryMovementService,
) *OrderProcessorImpl {
	return &OrderProcessorImpl{
		orderRepo:                orderRepo,
		orderItemRepo:            orderItemRepo,
		paymentRepo:              paymentRepo,
		paymentOrderItemRepo:     paymentOrderItemRepo,
		productRepo:              productRepo,
		paymentMethodRepo:        paymentMethodRepo,
		inventoryRepo:            inventoryRepo,
		inventoryMovementRepo:    inventoryMovementRepo,
		productVariantRepo:       productVariantRepo,
		outletRepo:               outletRepo,
		customerRepo:             customerRepo,
		splitBillProcessor:       NewSplitBillProcessorImpl(orderRepo, orderItemRepo, paymentRepo, paymentOrderItemRepo, outletRepo),
		txManager:                txManager,
		productRecipeRepo:        productRecipeRepo,
		ingredientRepo:           ingredientRepo,
		inventoryMovementService: inventoryMovementService,
	}
}

func (p *OrderProcessorImpl) CreateOrder(ctx context.Context, req *models.CreateOrderRequest, organizationID uuid.UUID) (*models.OrderResponse, error) {
	orderNumber, err := p.orderRepo.GetNextOrderNumber(ctx, organizationID, req.OutletID)
	if err != nil {
		return nil, fmt.Errorf("failed to generate order number: %w", err)
	}

	outlet, err := p.outletRepo.GetByID(ctx, req.OutletID)
	if err != nil {
		return nil, fmt.Errorf("outlet not found: %w", err)
	}

	var subtotal, totalCost float64
	var orderItems []*entities.OrderItem

	for _, itemReq := range req.OrderItems {
		product, err := p.productRepo.GetByID(ctx, itemReq.ProductID)
		if err != nil {
			return nil, fmt.Errorf("product not found: %w", err)
		}

		unitPrice := product.Price
		unitCost := product.Cost

		if itemReq.ProductVariantID != nil {
			variant, err := p.productVariantRepo.GetByID(ctx, *itemReq.ProductVariantID)
			if err != nil {
				return nil, fmt.Errorf("product variant not found: %w", err)
			}

			if variant.ProductID != itemReq.ProductID {
				return nil, fmt.Errorf("product variant does not belong to the specified product")
			}

			unitPrice += variant.PriceModifier
			if variant.Cost > 0 {
				unitCost = variant.Cost
			}
		}

		itemTotalPrice := float64(itemReq.Quantity) * unitPrice
		itemTotalCost := float64(itemReq.Quantity) * unitCost

		subtotal += itemTotalPrice
		totalCost += itemTotalCost

		orderItem := &entities.OrderItem{
			ProductID:        itemReq.ProductID,
			ProductVariantID: itemReq.ProductVariantID,
			Quantity:         itemReq.Quantity,
			UnitPrice:        unitPrice,
			TotalPrice:       itemTotalPrice,
			UnitCost:         unitCost,
			TotalCost:        itemTotalCost,
			Modifiers:        entities.Modifiers(itemReq.Modifiers),
			Notes:            itemReq.Notes,
			Metadata:         entities.Metadata(itemReq.Metadata),
			Status:           entities.OrderItemStatusPending,
		}

		orderItems = append(orderItems, orderItem)
	}

	taxAmount := subtotal * outlet.TaxRate
	totalAmount := subtotal + taxAmount

	metadata := entities.Metadata(req.Metadata)
	if req.CustomerName != nil {
		if metadata == nil {
			metadata = make(entities.Metadata)
		}
		metadata["customer_name"] = *req.CustomerName
	}
	order := &entities.Order{
		OrganizationID:  organizationID,
		OutletID:        req.OutletID,
		UserID:          req.UserID,
		CustomerID:      req.CustomerID,
		OrderNumber:     orderNumber,
		TableNumber:     req.TableNumber,
		OrderType:       entities.OrderType(req.OrderType),
		Status:          entities.OrderStatusPending,
		Subtotal:        subtotal,
		TaxAmount:       taxAmount,
		DiscountAmount:  0,
		TotalAmount:     totalAmount,
		TotalCost:       totalCost,
		RemainingAmount: totalAmount, // Initialize remaining amount equal to total amount
		PaymentStatus:   entities.PaymentStatusPending,
		IsVoid:          false,
		IsRefund:        false,
		Metadata:        metadata,
	}

	if err := p.orderRepo.Create(ctx, order); err != nil {
		return nil, fmt.Errorf("failed to create order: %w", err)
	}

	for _, orderItem := range orderItems {
		orderItem.OrderID = order.ID
		if err := p.orderItemRepo.Create(ctx, orderItem); err != nil {
			return nil, fmt.Errorf("failed to create order item: %w", err)
		}
	}

	orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, order.ID)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve created order: %w", err)
	}

	response := mappers.OrderEntityToResponse(orderWithRelations)
	return response, nil
}

func (p *OrderProcessorImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error) {
	order, err := p.orderRepo.GetByID(ctx, orderID)
	if err != nil {
		return nil, fmt.Errorf("order not found: %w", err)
	}

	if order.IsVoid {
		return nil, fmt.Errorf("cannot modify voided order")
	}

	if order.PaymentStatus == entities.PaymentStatusCompleted {
		return nil, fmt.Errorf("cannot modify fully paid order")
	}

	// Get outlet information for tax rate
	outlet, err := p.outletRepo.GetByID(ctx, order.OutletID)
	if err != nil {
		return nil, fmt.Errorf("outlet not found: %w", err)
	}

	var newSubtotal, newTotalCost float64
	var addedOrderItems []*entities.OrderItem

	for _, itemReq := range req.OrderItems {
		product, err := p.productRepo.GetByID(ctx, itemReq.ProductID)
		if err != nil {
			return nil, fmt.Errorf("product not found: %w", err)
		}

		// Use product price from database
		unitPrice := product.Price
		unitCost := product.Cost

		// Handle product variant if specified
		if itemReq.ProductVariantID != nil {
			variant, err := p.productVariantRepo.GetByID(ctx, *itemReq.ProductVariantID)
			if err != nil {
				return nil, fmt.Errorf("product variant not found: %w", err)
			}

			// Verify variant belongs to the product
			if variant.ProductID != itemReq.ProductID {
				return nil, fmt.Errorf("product variant does not belong to the specified product")
			}

			// Apply price modifier
			unitPrice += variant.PriceModifier
			// Use variant cost if available, otherwise use product cost
			if variant.Cost > 0 {
				unitCost = variant.Cost
			}
		}

		itemTotalPrice := float64(itemReq.Quantity) * unitPrice
		itemTotalCost := float64(itemReq.Quantity) * unitCost

		newSubtotal += itemTotalPrice
		newTotalCost += itemTotalCost

		orderItem := &entities.OrderItem{
			OrderID:          orderID,
			ProductID:        itemReq.ProductID,
			ProductVariantID: itemReq.ProductVariantID,
			Quantity:         itemReq.Quantity,
			UnitPrice:        unitPrice, // Use price from database
			TotalPrice:       itemTotalPrice,
			UnitCost:         unitCost,
			TotalCost:        itemTotalCost,
			Modifiers:        entities.Modifiers(itemReq.Modifiers),
			Notes:            itemReq.Notes,
			Metadata:         entities.Metadata(itemReq.Metadata),
			Status:           entities.OrderItemStatusPending,
		}

		addedOrderItems = append(addedOrderItems, orderItem)
	}

	order.Subtotal += newSubtotal
	order.TotalCost += newTotalCost
	// Recalculate tax amount using outlet's tax rate
	order.TaxAmount = order.Subtotal * outlet.TaxRate
	order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount

	// Recalculate remaining amount when items are added
	totalPaid, err := p.paymentRepo.GetTotalPaidByOrderID(ctx, orderID)
	if err != nil {
		return nil, fmt.Errorf("failed to get total paid amount: %w", err)
	}

	order.RemainingAmount = order.TotalAmount - totalPaid
	if order.RemainingAmount < 0 {
		order.RemainingAmount = 0
	}

	if req.Metadata != nil {
		if order.Metadata == nil {
			order.Metadata = make(entities.Metadata)
		}
		for k, v := range req.Metadata {
			order.Metadata[k] = v
		}
	}

	if err := p.orderRepo.Update(ctx, order); err != nil {
		return nil, fmt.Errorf("failed to update order: %w", err)
	}

	var addedItemResponses []models.OrderItemResponse
	for _, orderItem := range addedOrderItems {
		if err := p.orderItemRepo.Create(ctx, orderItem); err != nil {
			return nil, fmt.Errorf("failed to create order item: %w", err)
		}

		itemResponse := models.OrderItemResponse{
			ID:                  orderItem.ID,
			OrderID:             orderItem.OrderID,
			ProductID:           orderItem.ProductID,
			ProductVariantID:    orderItem.ProductVariantID,
			Quantity:            orderItem.Quantity,
			UnitPrice:           orderItem.UnitPrice,
			TotalPrice:          orderItem.TotalPrice,
			UnitCost:            orderItem.UnitCost,
			TotalCost:           orderItem.TotalCost,
			RefundAmount:        orderItem.RefundAmount,
			RefundQuantity:      orderItem.RefundQuantity,
			IsPartiallyRefunded: orderItem.IsPartiallyRefunded,
			IsFullyRefunded:     orderItem.IsFullyRefunded,
			RefundReason:        orderItem.RefundReason,
			RefundedAt:          orderItem.RefundedAt,
			RefundedBy:          orderItem.RefundedBy,
			Modifiers:           []map[string]interface{}(orderItem.Modifiers),
			Notes:               orderItem.Notes,
			Metadata:            map[string]interface{}(orderItem.Metadata),
			Status:              constants.OrderItemStatus(orderItem.Status),
			CreatedAt:           orderItem.CreatedAt,
			UpdatedAt:           orderItem.UpdatedAt,
		}
		addedItemResponses = append(addedItemResponses, itemResponse)
	}

	orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, orderID)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve updated order: %w", err)
	}

	updatedOrderResponse := mappers.OrderEntityToResponse(orderWithRelations)

	return &models.AddToOrderResponse{
		OrderID:      orderID,
		OrderNumber:  order.OrderNumber,
		AddedItems:   addedItemResponses,
		UpdatedOrder: *updatedOrderResponse,
	}, nil
}

func (p *OrderProcessorImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error) {
	// Get existing order
	order, err := p.orderRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("order not found: %w", err)
	}

	// Check if order can be modified
	if order.IsVoid {
		return nil, fmt.Errorf("cannot modify voided order")
	}

	// Apply updates
	if req.TableNumber != nil {
		order.TableNumber = req.TableNumber
	}
	if req.Status != nil {
		order.Status = entities.OrderStatus(*req.Status)
	}
	if req.DiscountAmount != nil {
		order.DiscountAmount = *req.DiscountAmount
		// Recalculate total amount
		order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount

		// Recalculate remaining amount when discount is applied
		totalPaid, err := p.paymentRepo.GetTotalPaidByOrderID(ctx, id)
		if err != nil {
			return nil, fmt.Errorf("failed to get total paid amount: %w", err)
		}

		order.RemainingAmount = order.TotalAmount - totalPaid
		if order.RemainingAmount < 0 {
			order.RemainingAmount = 0
		}
	}
	if req.Metadata != nil {
		if order.Metadata == nil {
			order.Metadata = make(entities.Metadata)
		}
		for k, v := range req.Metadata {
			order.Metadata[k] = v
		}
	}

	order.Status = entities.OrderStatusCompleted
	order.PaymentStatus = entities.PaymentStatusCompleted

	if err := p.orderRepo.UpdateStatusSuccess(ctx, order.ID, order.Status, order.PaymentStatus); err != nil {
		return nil, fmt.Errorf("failed to update order: %w", err)
	}

	orderWithRelations, err := p.orderRepo.GetWithRelations(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve updated order: %w", err)
	}

	response := mappers.OrderEntityToResponse(orderWithRelations)
	return response, nil
}

func (p *OrderProcessorImpl) GetOrderByID(ctx context.Context, id uuid.UUID) (*models.OrderResponse, error) {
	order, err := p.orderRepo.GetWithRelations(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("order not found: %w", err)
	}

	response := mappers.OrderEntityToResponse(order)
	return response, nil
}

func (p *OrderProcessorImpl) ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error) {
	filters := make(map[string]interface{})
	if req.OrganizationID != nil {
		filters["organization_id"] = *req.OrganizationID
	}
	if req.OutletID != nil {
		filters["outlet_id"] = *req.OutletID
	}
	if req.UserID != nil {
		filters["user_id"] = *req.UserID
	}
	if req.CustomerID != nil {
		filters["customer_id"] = *req.CustomerID
	}
	if req.OrderType != nil {
		filters["order_type"] = string(*req.OrderType)
	}
	if req.Status != nil {
		filters["status"] = string(*req.Status)
	}
	if req.PaymentStatus != nil {
		filters["payment_status"] = string(*req.PaymentStatus)
	}
	if req.IsVoid != nil {
		filters["is_void"] = *req.IsVoid
	}
	if req.IsRefund != nil {
		filters["is_refund"] = *req.IsRefund
	}
	if req.DateFrom != nil {
		filters["date_from"] = *req.DateFrom
	}
	if req.DateTo != nil {
		filters["date_to"] = *req.DateTo
	}
	if req.Search != "" {
		filters["search"] = req.Search
	}

	offset := (req.Page - 1) * req.Limit
	orders, total, err := p.orderRepo.List(ctx, filters, req.Limit, offset)
	if err != nil {
		return nil, fmt.Errorf("failed to list orders: %w", err)
	}

	orderResponses := make([]models.OrderResponse, len(orders))
	allPayments := make([]models.PaymentResponse, 0)

	for i, order := range orders {
		response := mappers.OrderEntityToResponse(order)
		if response != nil {
			orderResponses[i] = *response
			// Add payments from this order to the allPayments list
			if response.Payments != nil {
				allPayments = append(allPayments, response.Payments...)
			}
		}
	}

	totalPages := int(total) / req.Limit
	if int(total)%req.Limit > 0 {
		totalPages++
	}

	return &models.ListOrdersResponse{
		Orders:     orderResponses,
		Payments:   allPayments,
		TotalCount: int(total),
		Page:       req.Page,
		Limit:      req.Limit,
		TotalPages: totalPages,
	}, nil
}

func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID) error {
	if req.OrderID != req.OrderID {
		return fmt.Errorf("order ID mismatch: path parameter does not match request body")
	}

	order, err := p.orderRepo.GetByID(ctx, req.OrderID)
	if err != nil {
		return fmt.Errorf("order not found: %w", err)
	}

	if order.IsVoid {
		return fmt.Errorf("order is already voided")
	}

	if order.PaymentStatus == entities.PaymentStatusCompleted {
		return fmt.Errorf("cannot void fully paid order")
	}

	if req.Type == "ALL" {
		if err := p.orderRepo.VoidOrderWithStatus(ctx, req.OrderID, entities.OrderStatusCancelled, req.Reason, voidedBy); err != nil {
			return fmt.Errorf("failed to void order: %w", err)
		}
	} else if req.Type == "ITEM" {
		if len(req.Items) == 0 {
			return fmt.Errorf("items list is required when voiding specific items")
		}

		var totalVoidedAmount float64
		var totalVoidedCost float64

		for _, itemVoid := range req.Items {
			orderItemID := itemVoid.OrderItemID

			orderItem, err := p.orderItemRepo.GetByID(ctx, orderItemID)
			if err != nil {
				return fmt.Errorf("order item not found: %w", err)
			}

			if orderItem.OrderID != req.OrderID {
				return fmt.Errorf("order item does not belong to this order")
			}

			if itemVoid.Quantity > orderItem.Quantity {
				return fmt.Errorf("void quantity cannot exceed original quantity for item %d", itemVoid.OrderItemID)
			}

			voidedAmount := float64(itemVoid.Quantity) * orderItem.UnitPrice
			voidedCost := float64(itemVoid.Quantity) * orderItem.UnitCost

			totalVoidedAmount += voidedAmount
			totalVoidedCost += voidedCost

			if err := p.orderItemRepo.VoidOrderItem(ctx, orderItemID, itemVoid.Quantity, req.Reason, voidedBy); err != nil {
				return fmt.Errorf("failed to void order item %d: %w", itemVoid.OrderItemID, err)
			}
		}

		outlet, err := p.outletRepo.GetByID(ctx, order.OutletID)
		if err != nil {
			return fmt.Errorf("outlet not found: %w", err)
		}

		order.Subtotal -= totalVoidedAmount
		order.TotalCost -= totalVoidedCost
		order.TaxAmount = order.Subtotal * outlet.TaxRate // Recalculate tax using outlet's tax rate
		order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount

		if err := p.orderRepo.Update(ctx, order); err != nil {
			return fmt.Errorf("failed to update order totals: %w", err)
		}

		remainingItems, err := p.orderItemRepo.GetByOrderID(ctx, req.OrderID)
		if err != nil {
			return fmt.Errorf("failed to get remaining order items: %w", err)
		}

		hasActiveItems := false
		for _, item := range remainingItems {
			if item.Status != entities.OrderItemStatusCancelled {
				hasActiveItems = true
				break
			}
		}

		if !hasActiveItems {
			if err := p.orderRepo.VoidOrderWithStatus(ctx, req.OrderID, entities.OrderStatusCancelled, req.Reason, voidedBy); err != nil {
				return fmt.Errorf("failed to void order after all items voided: %w", err)
			}
		}
	} else {
		return fmt.Errorf("invalid void type: must be 'ALL' or 'ITEM'")
	}

	return nil
}

func (p *OrderProcessorImpl) RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID) error {
	order, err := p.orderRepo.GetWithRelations(ctx, id)
	if err != nil {
		return fmt.Errorf("order not found: %w", err)
	}

	if order.IsRefund {
		return fmt.Errorf("order is already refunded")
	}

	if order.PaymentStatus != entities.PaymentStatusCompleted {
		return fmt.Errorf("order is not paid, cannot refund")
	}

	reason := "No reason provided"
	if req.Reason != nil {
		reason = *req.Reason
	}

	// Process refund based on request type
	if req.RefundAmount != nil {
		// Full or partial refund by amount
		if *req.RefundAmount > order.TotalAmount {
			return fmt.Errorf("refund amount cannot exceed order total")
		}

		// Update order refund amount
		order.RefundAmount = *req.RefundAmount
		if err := p.orderRepo.Update(ctx, order); err != nil {
			return fmt.Errorf("failed to update order refund amount: %w", err)
		}

		// Mark order as refunded
		if err := p.orderRepo.RefundOrder(ctx, id, reason, refundedBy); err != nil {
			return fmt.Errorf("failed to mark order as refunded: %w", err)
		}

	} else if len(req.OrderItems) > 0 {
		// Refund by specific items
		totalRefundAmount := float64(0)

		for _, itemRefund := range req.OrderItems {
			// Get order item
			orderItem, err := p.orderItemRepo.GetByID(ctx, itemRefund.OrderItemID)
			if err != nil {
				return fmt.Errorf("order item not found: %w", err)
			}

			if orderItem.OrderID != id {
				return fmt.Errorf("order item does not belong to this order")
			}

			// Calculate refund amount for this item
			refundQuantity := itemRefund.RefundQuantity
			if refundQuantity == 0 {
				refundQuantity = orderItem.Quantity
			}

			if refundQuantity > orderItem.Quantity {
				return fmt.Errorf("refund quantity cannot exceed original quantity")
			}

			refundAmount := float64(refundQuantity) * orderItem.UnitPrice
			if itemRefund.RefundAmount != nil {
				refundAmount = *itemRefund.RefundAmount
			}

			// Process item refund
			itemReason := reason
			if itemRefund.Reason != nil {
				itemReason = *itemRefund.Reason
			}

			if err := p.orderItemRepo.RefundOrderItem(ctx, itemRefund.OrderItemID, refundQuantity, refundAmount, itemReason, refundedBy); err != nil {
				return fmt.Errorf("failed to refund order item: %w", err)
			}

			totalRefundAmount += refundAmount
		}

		// Update order refund amount
		order.RefundAmount = totalRefundAmount
		if err := p.orderRepo.Update(ctx, order); err != nil {
			return fmt.Errorf("failed to update order refund amount: %w", err)
		}

		// Mark order as refunded
		if err := p.orderRepo.RefundOrder(ctx, id, reason, refundedBy); err != nil {
			return fmt.Errorf("failed to mark order as refunded: %w", err)
		}
	}

	return nil
}

func (p *OrderProcessorImpl) CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error) {
	order, err := p.orderRepo.GetByID(ctx, req.OrderID)
	if err != nil {
		return nil, fmt.Errorf("order not found: %w", err)
	}

	if order.IsVoid {
		return nil, fmt.Errorf("cannot process payment for voided order")
	}

	if order.PaymentStatus == entities.PaymentStatusCompleted {
		return nil, fmt.Errorf("order is already fully paid")
	}

	_, err = p.paymentMethodRepo.GetByID(ctx, req.PaymentMethodID)
	if err != nil {
		return nil, fmt.Errorf("payment method not found: %w", err)
	}

	totalPaid, err := p.paymentRepo.GetTotalPaidByOrderID(ctx, req.OrderID)
	if err != nil {
		return nil, fmt.Errorf("failed to get total paid: %w", err)
	}

	payment, err := p.CreatePaymentWithInventoryMovement(ctx, req, order, totalPaid)
	if err != nil {
		return nil, err
	}

	paymentWithRelations, err := p.paymentRepo.GetByID(ctx, payment.ID)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve created payment: %w", err)
	}

	response := mappers.PaymentEntityToResponse(paymentWithRelations)
	return response, nil
}

func (p *OrderProcessorImpl) RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error {
	payment, err := p.paymentRepo.GetByID(ctx, paymentID)
	if err != nil {
		return fmt.Errorf("payment not found: %w", err)
	}

	if payment.Status != entities.PaymentTransactionStatusCompleted {
		return fmt.Errorf("payment is not completed, cannot refund")
	}

	if refundAmount > payment.Amount {
		return fmt.Errorf("refund amount cannot exceed payment amount")
	}

	return p.RefundPaymentWithInventoryMovement(ctx, paymentID, refundAmount, reason, refundedBy, payment)
}

func (p *OrderProcessorImpl) CreatePaymentWithInventoryMovement(ctx context.Context, req *models.CreatePaymentRequest, order *entities.Order, totalPaid float64) (*entities.Payment, error) {
	var payment *entities.Payment

	err := p.txManager.WithTransaction(ctx, func(ctx context.Context) error {
		var err error
		payment, err = p.createPayment(ctx, req)
		if err != nil {
			return fmt.Errorf("failed to create payment: %w", err)
		}

		if err := p.updateOrderStatus(ctx, req.OrderID); err != nil {
			return fmt.Errorf("failed to update order status: %w", err)
		}

		if err := p.processInventoryAdjustments(ctx, req.OrderID, order, payment); err != nil {
			return fmt.Errorf("failed to process inventory adjustments: %w", err)
		}

		return nil
	})

	if err != nil {
		return nil, err
	}

	return payment, nil
}

func (p *OrderProcessorImpl) createPayment(ctx context.Context, req *models.CreatePaymentRequest) (*entities.Payment, error) {
	payment := &entities.Payment{
		OrderID:          req.OrderID,
		PaymentMethodID:  req.PaymentMethodID,
		Amount:           req.Amount,
		Status:           entities.PaymentTransactionStatusCompleted,
		TransactionID:    req.TransactionID,
		SplitNumber:      req.SplitNumber,
		SplitTotal:       req.SplitTotal,
		SplitDescription: req.SplitDescription,
		Metadata:         entities.Metadata(req.Metadata),
	}

	if err := p.paymentRepo.Create(ctx, payment); err != nil {
		return nil, fmt.Errorf("failed to create payment: %w", err)
	}

	return payment, nil
}

func (p *OrderProcessorImpl) updateOrderStatus(ctx context.Context, orderID uuid.UUID) error {
	orderUpdate := &entities.Order{
		ID:            orderID,
		Status:        entities.OrderStatusCompleted,
		PaymentStatus: entities.PaymentStatusCompleted,
	}

	if err := p.orderRepo.UpdateStatusSuccess(ctx, orderID, orderUpdate.Status, orderUpdate.PaymentStatus); err != nil {
		return fmt.Errorf("failed to update order status: %w", err)
	}

	return nil
}

func (p *OrderProcessorImpl) processInventoryAdjustments(ctx context.Context, orderID uuid.UUID, order *entities.Order, payment *entities.Payment) error {
	orderItems, err := p.orderItemRepo.GetByOrderID(ctx, orderID)
	if err != nil {
		return fmt.Errorf("failed to get order items for inventory adjustment: %w", err)
	}

	var inventoryMovements []*entities.InventoryMovement
	var inventoryUpdates []*entities.Inventory
	var ingredientUpdates []*entities.Ingredient
	var ingredientMovements []*entities.InventoryMovement

	for _, item := range orderItems {
		updatedInventory, err := p.prepareProductInventoryUpdate(ctx, item, order.OutletID)
		if err != nil {
			return fmt.Errorf("failed to prepare product inventory update for product %s: %w", item.ProductID, err)
		}
		inventoryUpdates = append(inventoryUpdates, updatedInventory)

		productMovement := p.prepareProductInventoryMovement(item, order, payment, updatedInventory)
		inventoryMovements = append(inventoryMovements, productMovement)

		ingredientData, err := p.prepareIngredientRecipeData(ctx, item, order, payment)
		if err != nil {
			return fmt.Errorf("failed to prepare ingredient recipe data for product %s: %w", item.ProductID, err)
		}

		ingredientUpdates = append(ingredientUpdates, ingredientData.ingredientUpdates...)
		ingredientMovements = append(ingredientMovements, ingredientData.movements...)
	}

	if len(inventoryUpdates) > 0 {
		if err := p.bulkUpdateInventory(ctx, inventoryUpdates); err != nil {
			return fmt.Errorf("failed to bulk update product inventory: %w", err)
		}
	}

	allMovements := append(inventoryMovements, ingredientMovements...)
	if len(allMovements) > 0 {
		if err := p.bulkCreateInventoryMovements(ctx, allMovements); err != nil {
			return fmt.Errorf("failed to bulk create inventory movements: %w", err)
		}
	}

	return nil
}

// adjustIngredientInventoryWithTransaction adjusts ingredient inventory within a transaction
func (p *OrderProcessorImpl) adjustIngredientInventoryWithTransaction(ctx context.Context, ingredientID, outletID uuid.UUID, delta float64) (*entities.Inventory, error) {
	var inventory entities.Inventory

	// Try to get existing ingredient inventory
	existingInventory, err := p.inventoryRepo.GetByProductAndOutlet(ctx, ingredientID, outletID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// Create new ingredient inventory record if it doesn't exist
			inventory = entities.Inventory{
				ProductID:    ingredientID,
				OutletID:     outletID,
				Quantity:     0,
				ReorderLevel: 0,
			}
			if err := p.inventoryRepo.Create(ctx, &inventory); err != nil {
				return nil, fmt.Errorf("failed to create ingredient inventory record: %w", err)
			}
		} else {
			return nil, err
		}
	} else {
		// Use existing ingredient inventory
		inventory = *existingInventory
	}

	// Update quantity (note: ingredients use float64 quantities, but inventory uses int)
	// Convert delta to int for inventory system
	deltaInt := int(delta)
	inventory.Quantity += deltaInt
	if inventory.Quantity < 0 {
		inventory.Quantity = 0
	}

	if err := p.inventoryRepo.Update(ctx, &inventory); err != nil {
		return nil, err
	}

	return &inventory, nil
}

// createIngredientInventoryMovement creates an inventory movement record for ingredient usage
func (p *OrderProcessorImpl) createIngredientInventoryMovement(ctx context.Context, recipe *entities.ProductRecipe, item *entities.OrderItem, order *entities.Order, payment *entities.Payment, updatedInventory *entities.Inventory, totalIngredientQuantity float64) error {
	ingredient, err := p.getIngredientDetails(ctx, recipe.IngredientID, order.OrganizationID)
	if err != nil {
		return fmt.Errorf("failed to get ingredient details: %w", err)
	}

	movement := &entities.InventoryMovement{
		OrganizationID:   order.OrganizationID,
		OutletID:         order.OutletID,
		ItemID:           recipe.IngredientID,
		ItemType:         "INGREDIENT",
		MovementType:     entities.InventoryMovementTypeIngredient,
		Quantity:         -totalIngredientQuantity,                                          // Negative because we're consuming ingredients
		PreviousQuantity: float64(updatedInventory.Quantity + int(totalIngredientQuantity)), // Add back the quantity that was subtracted
		NewQuantity:      float64(updatedInventory.Quantity),
		UnitCost:         ingredient.Cost, // Use ingredient cost
		TotalCost:        totalIngredientQuantity * ingredient.Cost,
		ReferenceType: func() *entities.InventoryMovementReferenceType {
			t := entities.InventoryMovementReferenceTypePayment
			return &t
		}(),
		ReferenceID: &payment.ID,
		OrderID:     &order.ID,
		PaymentID:   &payment.ID,
		UserID:      order.UserID,
		Reason:      stringPtr("Ingredient consumption from order payment"),
		Notes:       stringPtr(fmt.Sprintf("Order: %s, Payment: %s, Product: %s, Ingredient: %s", order.OrderNumber, payment.ID, item.Product.Name, ingredient.Name)),
		Metadata:    entities.Metadata{"order_item_id": item.ID, "product_id": item.ProductID, "ingredient_id": recipe.IngredientID, "recipe_quantity": recipe.Quantity, "order_quantity": item.Quantity},
	}

	if err := p.inventoryMovementRepo.Create(ctx, movement); err != nil {
		return fmt.Errorf("failed to create ingredient inventory movement: %w", err)
	}

	return nil
}

// getIngredientDetails retrieves ingredient details for cost calculation
func (p *OrderProcessorImpl) getIngredientDetails(ctx context.Context, ingredientID uuid.UUID, organizationID uuid.UUID) (*entities.Ingredient, error) {
	ingredient, err := p.ingredientRepo.GetByID(ctx, ingredientID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get ingredient details: %w", err)
	}
	return ingredient, nil
}

// createInventoryMovement creates an inventory movement record for audit trail
func (p *OrderProcessorImpl) createInventoryMovement(ctx context.Context, item *entities.OrderItem, order *entities.Order, payment *entities.Payment, updatedInventory *entities.Inventory) error {
	movement := &entities.InventoryMovement{
		OrganizationID:   order.OrganizationID,
		OutletID:         order.OutletID,
		ItemID:           item.ProductID,
		ItemType:         "PRODUCT",
		MovementType:     entities.InventoryMovementTypeSale,
		Quantity:         float64(-item.Quantity),
		PreviousQuantity: float64(updatedInventory.Quantity + item.Quantity), // Add back the quantity that was subtracted
		NewQuantity:      float64(updatedInventory.Quantity),
		UnitCost:         item.UnitCost,
		TotalCost:        float64(item.Quantity) * item.UnitCost,
		ReferenceType: func() *entities.InventoryMovementReferenceType {
			t := entities.InventoryMovementReferenceTypePayment
			return &t
		}(),
		ReferenceID: &payment.ID,
		OrderID:     &order.ID,
		PaymentID:   &payment.ID,
		UserID:      order.UserID,
		Reason:      stringPtr("Sale from order payment"),
		Notes:       stringPtr(fmt.Sprintf("Order: %s, Payment: %s", order.OrderNumber, payment.ID)),
		Metadata:    entities.Metadata{"order_item_id": item.ID},
	}

	if err := p.inventoryMovementRepo.Create(ctx, movement); err != nil {
		return fmt.Errorf("failed to create inventory movement: %w", err)
	}

	return nil
}

func (p *OrderProcessorImpl) RefundPaymentWithInventoryMovement(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID, payment *entities.Payment) error {
	return p.txManager.WithTransaction(ctx, func(ctx context.Context) error {
		if err := p.processRefund(ctx, paymentID, refundAmount, reason, refundedBy); err != nil {
			return fmt.Errorf("failed to process refund: %w", err)
		}

		if err := p.updateOrderRefundAmount(ctx, payment.OrderID, refundAmount); err != nil {
			return fmt.Errorf("failed to update order refund amount: %w", err)
		}

		return nil
	})
}

func (p *OrderProcessorImpl) processRefund(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error {
	if err := p.paymentRepo.RefundPayment(ctx, paymentID, refundAmount, reason, refundedBy); err != nil {
		return fmt.Errorf("failed to refund payment: %w", err)
	}
	return nil
}

func (p *OrderProcessorImpl) updateOrderRefundAmount(ctx context.Context, orderID uuid.UUID, refundAmount float64) error {
	order, err := p.orderRepo.GetByID(ctx, orderID)
	if err != nil {
		return fmt.Errorf("failed to get order: %w", err)
	}

	order.RefundAmount += refundAmount
	if err := p.orderRepo.Update(ctx, order); err != nil {
		return fmt.Errorf("failed to update order refund amount: %w", err)
	}

	return nil
}

func (p *OrderProcessorImpl) SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error) {
	order, err := p.orderRepo.GetByID(ctx, orderID)
	if err != nil {
		return nil, fmt.Errorf("order not found: %w", err)
	}

	if order.OrganizationID != organizationID {
		return nil, fmt.Errorf("order does not belong to the organization")
	}

	if order.Status != entities.OrderStatusPending {
		return nil, fmt.Errorf("customer can only be set for pending orders")
	}

	// Verify customer exists and belongs to the organization
	customer, err := p.customerRepo.GetByIDAndOrganization(ctx, req.CustomerID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("customer not found or does not belong to the organization: %w", err)
	}

	// Update order with customer ID
	order.CustomerID = &req.CustomerID
	if err := p.orderRepo.Update(ctx, order); err != nil {
		return nil, fmt.Errorf("failed to update order with customer: %w", err)
	}

	response := &models.SetOrderCustomerResponse{
		OrderID:    orderID,
		CustomerID: req.CustomerID,
		Message:    fmt.Sprintf("Customer '%s' successfully set for order", customer.Name),
	}

	return response, nil
}

func (p *OrderProcessorImpl) SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error) {
	order, err := p.orderRepo.GetWithRelations(ctx, req.OrderID)
	if err != nil {
		return nil, fmt.Errorf("order not found: %w", err)
	}

	if order.IsVoid {
		return nil, fmt.Errorf("cannot split voided order")
	}

	if order.PaymentStatus == entities.PaymentStatusCompleted {
		return nil, fmt.Errorf("cannot split fully paid order")
	}

	existingPayments, err := p.paymentRepo.GetByOrderID(ctx, req.OrderID)
	if err != nil {
		return nil, fmt.Errorf("failed to get existing payments: %w", err)
	}

	var existingSplitType *entities.SplitType
	for _, payment := range existingPayments {
		if payment.SplitType != nil && payment.SplitTotal > 1 {
			existingSplitType = payment.SplitType
			break
		}
	}

	if existingSplitType != nil {
		requestedSplitType := entities.SplitTypeAmount
		if req.IsItem() {
			requestedSplitType = entities.SplitTypeItem
		}

		if *existingSplitType != requestedSplitType {
			return nil, fmt.Errorf("order already has %s split payments. Subsequent payments must use the same split type", *existingSplitType)
		}
	}

	payment, err := p.paymentMethodRepo.GetByID(ctx, req.PaymentMethodID)
	if err != nil {
		return nil, fmt.Errorf("payment method not found: %w", err)
	}

	customer := &entities.Customer{}
	if req.CustomerID != uuid.Nil {
		customer, err = p.customerRepo.GetByIDAndOrganization(ctx, req.CustomerID, order.OrganizationID)

		if err != nil && err != gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("customer not found or does not belong to the organization: %w", err)
		}
	}

	var response *models.SplitBillResponse
	if req.IsAmount() {
		response, err = p.splitBillProcessor.SplitByAmount(ctx, req, order, payment, customer)
	} else if req.IsItem() {
		response, err = p.splitBillProcessor.SplitByItem(ctx, req, order, payment, customer)
	} else {
		return nil, fmt.Errorf("invalid split type: must be AMOUNT or ITEM")
	}
	if err != nil {
		return nil, err
	}
	return response, nil
}

// Helper method to adjust inventory within a transaction
func (p *OrderProcessorImpl) adjustInventoryWithTransaction(ctx context.Context, productID, outletID uuid.UUID, delta int) (*entities.Inventory, error) {
	var inventory entities.Inventory

	// Try to get existing inventory
	existingInventory, err := p.inventoryRepo.GetByProductAndOutlet(ctx, productID, outletID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// Create new inventory record if it doesn't exist
			inventory = entities.Inventory{
				ProductID:    productID,
				OutletID:     outletID,
				Quantity:     0,
				ReorderLevel: 0,
			}
			if err := p.inventoryRepo.Create(ctx, &inventory); err != nil {
				return nil, fmt.Errorf("failed to create inventory record: %w", err)
			}
		} else {
			return nil, err
		}
	} else {
		// Use existing inventory
		inventory = *existingInventory
	}

	// Update quantity
	inventory.UpdateQuantity(delta)
	if err := p.inventoryRepo.Update(ctx, &inventory); err != nil {
		return nil, err
	}

	return &inventory, nil
}

func (p *OrderProcessorImpl) createIngredientProductMovement(ctx context.Context, recipe *entities.ProductRecipe, item *entities.OrderItem, order *entities.Order, payment *entities.Payment, totalIngredientQuantity float64) error {
	ingredient, err := p.getIngredientDetails(ctx, recipe.IngredientID, order.OrganizationID)
	if err != nil {
		return fmt.Errorf("failed to get ingredient details: %w", err)
	}

	err = p.inventoryMovementService.CreateProductMovement(
		ctx,
		recipe.IngredientID, // ingredientID as productID
		order.OrganizationID,
		order.OutletID,
		order.UserID,
		entities.InventoryMovementTypeSale, // Movement Type "Sales"
		-totalIngredientQuantity,           // Negative quantity for consumption
		ingredient.Cost,                    // Unit cost from ingredient
		"Ingredient consumption from order payment",
		func() *entities.InventoryMovementReferenceType {
			t := entities.InventoryMovementReferenceTypePayment
			return &t
		}(),
		&payment.ID,
	)

	if err != nil {
		return fmt.Errorf("failed to create ingredient product movement: %w", err)
	}

	return nil
}

// createRefundIngredientProductMovement creates a product movement record for ingredient with Movement Type "Refund" and ItemType "INGREDIENT"
func (p *OrderProcessorImpl) createRefundIngredientProductMovement(ctx context.Context, recipe *entities.ProductRecipe, item *entities.OrderItem, order *entities.Order, payment *entities.Payment, totalIngredientQuantity float64, refundRatio float64, reason string, refundedBy uuid.UUID, refundAmount float64) error {
	// Get ingredient details for cost calculation
	ingredient, err := p.getIngredientDetails(ctx, recipe.IngredientID, order.OrganizationID)
	if err != nil {
		return fmt.Errorf("failed to get ingredient details: %w", err)
	}

	// Create product movement using the inventory movement service
	err = p.inventoryMovementService.CreateProductMovement(
		ctx,
		recipe.IngredientID, // ingredientID as productID
		order.OrganizationID,
		order.OutletID,
		order.UserID,
		entities.InventoryMovementTypeRefund, // Movement Type "Refund"
		totalIngredientQuantity,              // Positive quantity for restoration
		ingredient.Cost,                      // Unit cost from ingredient
		fmt.Sprintf("Ingredient restoration from order refund: %s", reason),
		func() *entities.InventoryMovementReferenceType {
			t := entities.InventoryMovementReferenceTypeRefund
			return &t
		}(),
		&payment.ID,
	)

	if err != nil {
		return fmt.Errorf("failed to create refund ingredient product movement: %w", err)
	}

	return nil
}

// prepareProductInventoryUpdate prepares inventory update data without making database calls
func (p *OrderProcessorImpl) prepareProductInventoryUpdate(ctx context.Context, item *entities.OrderItem, outletID uuid.UUID) (*entities.Inventory, error) {
	// Get current inventory
	currentInventory, err := p.inventoryRepo.GetByProductAndOutlet(ctx, item.ProductID, outletID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// Create new inventory record if it doesn't exist
			currentInventory = &entities.Inventory{
				ProductID:    item.ProductID,
				OutletID:     outletID,
				Quantity:     0,
				ReorderLevel: 0,
			}
		} else {
			return nil, err
		}
	}

	// Calculate new quantity
	newQuantity := currentInventory.Quantity - item.Quantity
	if newQuantity < 0 {
		newQuantity = 0
	}

	// Update the inventory object (don't save to DB yet)
	currentInventory.Quantity = newQuantity
	return currentInventory, nil
}

// prepareProductInventoryMovement prepares inventory movement data without making database calls
func (p *OrderProcessorImpl) prepareProductInventoryMovement(item *entities.OrderItem, order *entities.Order, payment *entities.Payment, updatedInventory *entities.Inventory) *entities.InventoryMovement {
	previousQuantity := updatedInventory.Quantity + item.Quantity

	return &entities.InventoryMovement{
		OrganizationID:   order.OrganizationID,
		OutletID:         order.OutletID,
		ItemID:           item.ProductID,
		ItemType:         "PRODUCT",
		MovementType:     entities.InventoryMovementTypeSale,
		Quantity:         float64(-item.Quantity),
		PreviousQuantity: float64(previousQuantity),
		NewQuantity:      float64(updatedInventory.Quantity),
		UnitCost:         item.UnitCost,
		TotalCost:        float64(item.Quantity) * item.UnitCost,
		ReferenceType: func() *entities.InventoryMovementReferenceType {
			t := entities.InventoryMovementReferenceTypePayment
			return &t
		}(),
		ReferenceID: &payment.ID,
		OrderID:     &order.ID,
		PaymentID:   &payment.ID,
		UserID:      order.UserID,
		Reason:      stringPtr("Sale from order payment"),
		Notes:       stringPtr(fmt.Sprintf("Order: %s, Payment: %s", order.OrderNumber, payment.ID)),
		Metadata:    entities.Metadata{"order_item_id": item.ID},
	}
}

// ingredientRecipeData holds the collected data for ingredient recipes
type ingredientRecipeData struct {
	ingredientUpdates []*entities.Ingredient
	movements         []*entities.InventoryMovement
}

// prepareIngredientRecipeData prepares ingredient recipe data without making database calls
func (p *OrderProcessorImpl) prepareIngredientRecipeData(ctx context.Context, item *entities.OrderItem, order *entities.Order, payment *entities.Payment) (*ingredientRecipeData, error) {
	var recipes []*entities.ProductRecipe
	var err error
	if item.ProductVariantID != nil {
		recipes, err = p.productRecipeRepo.GetByProductAndVariantID(ctx, item.ProductID, item.ProductVariantID, order.OrganizationID)
	} else {
		recipes, err = p.productRecipeRepo.GetByProductAndVariantID(ctx, item.ProductID, nil, order.OrganizationID)
	}

	if err != nil {
		return nil, fmt.Errorf("failed to get product recipes: %w", err)
	}

	if len(recipes) == 0 {
		return &ingredientRecipeData{}, nil // No recipes found
	}

	var ingredientUpdates []*entities.Ingredient
	var movements []*entities.InventoryMovement

	for _, recipe := range recipes {
		ingredientData, err := p.prepareIngredientRecipeItem(ctx, recipe, item, order, payment)
		if err != nil {
			return nil, fmt.Errorf("failed to prepare ingredient recipe item: %w", err)
		}

		ingredientUpdates = append(ingredientUpdates, ingredientData.ingredient)
		movements = append(movements, ingredientData.movement)
	}

	return &ingredientRecipeData{
		ingredientUpdates: ingredientUpdates,
		movements:         movements,
	}, nil
}

// ingredientRecipeItem holds data for a single ingredient recipe item
type ingredientRecipeItem struct {
	ingredient *entities.Ingredient
	movement   *entities.InventoryMovement
}

// prepareIngredientRecipeItem prepares data for a single ingredient recipe without making database calls
func (p *OrderProcessorImpl) prepareIngredientRecipeItem(ctx context.Context, recipe *entities.ProductRecipe, item *entities.OrderItem, order *entities.Order, payment *entities.Payment) (*ingredientRecipeItem, error) {
	totalIngredientQuantity := recipe.Quantity * float64(item.Quantity)

	currentIngredient, err := p.ingredientRepo.GetByID(ctx, recipe.IngredientID, order.OrganizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get ingredient: %w", err)
	}

	currentIngredient.Stock -= totalIngredientQuantity

	movement := &entities.InventoryMovement{
		OrganizationID:   order.OrganizationID,
		OutletID:         order.OutletID,
		ItemID:           recipe.IngredientID,
		ItemType:         "INGREDIENT",
		MovementType:     entities.InventoryMovementTypeIngredient,
		Quantity:         -totalIngredientQuantity,
		PreviousQuantity: 0,
		NewQuantity:      0,
		UnitCost:         currentIngredient.Cost,
		TotalCost:        totalIngredientQuantity * currentIngredient.Cost,
		ReferenceType: func() *entities.InventoryMovementReferenceType {
			t := entities.InventoryMovementReferenceTypePayment
			return &t
		}(),
		ReferenceID: &payment.ID,
		OrderID:     &order.ID,
		PaymentID:   &payment.ID,
		UserID:      order.UserID,
		Reason:      stringPtr("Ingredient consumption from order payment"),
		Notes:       stringPtr(fmt.Sprintf("Order: %s, Payment: %s, Product: %s", order.OrderNumber, payment.ID, item.Product.Name)),
		Metadata:    entities.Metadata{"order_item_id": item.ID, "product_id": item.ProductID, "ingredient_id": recipe.IngredientID, "recipe_quantity": recipe.Quantity, "order_quantity": item.Quantity},
	}

	return &ingredientRecipeItem{
		ingredient: currentIngredient, // Return unchanged ingredient
		movement:   movement,
	}, nil
}

// bulkUpdateInventory performs bulk update of inventory records
func (p *OrderProcessorImpl) bulkUpdateInventory(ctx context.Context, inventories []*entities.Inventory) error {
	if len(inventories) == 0 {
		return nil
	}

	// Use the repository's bulk update method
	return p.inventoryRepo.BulkUpdate(ctx, inventories)
}

// bulkCreateInventoryMovements performs bulk creation of inventory movement records
func (p *OrderProcessorImpl) bulkCreateInventoryMovements(ctx context.Context, movements []*entities.InventoryMovement) error {
	if len(movements) == 0 {
		return nil
	}

	// Use GORM's CreateInBatches for true bulk creation
	// Convert to interface slice for GORM
	movementInterfaces := make([]interface{}, len(movements))
	for i, movement := range movements {
		movementInterfaces[i] = movement
	}

	// Use the inventory movement repository's bulk create method
	// Note: This assumes the repository has a bulk create method
	// If not, we can implement it here using GORM's CreateInBatches
	return p.inventoryMovementRepo.CreateInBatches(ctx, movements, 100)
}

// prepareRefundProductInventoryUpdate prepares product inventory restoration data without making database calls
func (p *OrderProcessorImpl) prepareRefundProductInventoryUpdate(ctx context.Context, item *entities.OrderItem, outletID uuid.UUID, refundedQuantity int) (*entities.Inventory, error) {
	// Get current inventory
	currentInventory, err := p.inventoryRepo.GetByProductAndOutlet(ctx, item.ProductID, outletID)
	if err != nil {
		return nil, fmt.Errorf("failed to get product inventory: %w", err)
	}

	// Calculate new quantity (restore the refunded quantity)
	newQuantity := currentInventory.Quantity + refundedQuantity

	// Update the inventory object (don't save to DB yet)
	currentInventory.Quantity = newQuantity
	return currentInventory, nil
}

// prepareRefundProductInventoryMovement prepares product inventory movement data for refunds
func (p *OrderProcessorImpl) prepareRefundProductInventoryMovement(item *entities.OrderItem, order *entities.Order, payment *entities.Payment, updatedInventory *entities.Inventory, refundedQuantity int, refundRatio float64, reason string, refundedBy uuid.UUID, refundAmount float64) *entities.InventoryMovement {
	previousQuantity := updatedInventory.Quantity - refundedQuantity

	return &entities.InventoryMovement{
		OrganizationID:   order.OrganizationID,
		OutletID:         order.OutletID,
		ItemID:           item.ProductID,
		ItemType:         "PRODUCT",
		MovementType:     entities.InventoryMovementTypeRefund,
		Quantity:         float64(refundedQuantity),
		PreviousQuantity: float64(previousQuantity),
		NewQuantity:      float64(updatedInventory.Quantity),
		UnitCost:         item.UnitCost,
		TotalCost:        float64(refundedQuantity) * item.UnitCost,
		ReferenceType: func() *entities.InventoryMovementReferenceType {
			t := entities.InventoryMovementReferenceTypeRefund
			return &t
		}(),
		ReferenceID: &payment.ID,
		OrderID:     &order.ID,
		PaymentID:   &payment.ID,
		UserID:      refundedBy,
		Reason:      stringPtr(fmt.Sprintf("Refund: %s", reason)),
		Notes:       stringPtr(fmt.Sprintf("Order: %s, Payment: %s, Refund Amount: %.2f", order.OrderNumber, payment.ID, refundAmount)),
		Metadata:    entities.Metadata{"order_item_id": item.ID, "refund_ratio": refundRatio},
	}
}

// prepareRefundedIngredientRecipeData prepares ingredient recipe restoration data for refunds
func (p *OrderProcessorImpl) prepareRefundedIngredientRecipeData(ctx context.Context, item *entities.OrderItem, order *entities.Order, payment *entities.Payment, refundRatio float64, reason string, refundedBy uuid.UUID, refundAmount float64) (*ingredientRecipeData, error) {
	// Check if the product has ingredients
	product, err := p.productRepo.GetByID(ctx, item.ProductID)
	if err != nil {
		return nil, fmt.Errorf("failed to get product: %w", err)
	}

	if !product.HasIngredients {
		return &ingredientRecipeData{}, nil // Product doesn't have ingredients
	}

	// Get product recipes based on variant (if any)
	var recipes []*entities.ProductRecipe
	if item.ProductVariantID != nil {
		recipes, err = p.productRecipeRepo.GetByProductAndVariantID(ctx, item.ProductID, item.ProductVariantID, order.OrganizationID)
	} else {
		recipes, err = p.productRecipeRepo.GetByProductAndVariantID(ctx, item.ProductID, nil, order.OrganizationID)
	}

	if err != nil {
		return nil, fmt.Errorf("failed to get product recipes: %w", err)
	}

	if len(recipes) == 0 {
		return &ingredientRecipeData{}, nil // No recipes found
	}

	var ingredientUpdates []*entities.Ingredient
	var movements []*entities.InventoryMovement

	// Process each ingredient in the recipe
	for _, recipe := range recipes {
		ingredientData, err := p.prepareRefundedIngredientRecipeItem(ctx, recipe, item, order, payment, refundRatio, reason, refundedBy, refundAmount)
		if err != nil {
			return nil, fmt.Errorf("failed to prepare ingredient recipe restoration: %w", err)
		}

		ingredientUpdates = append(ingredientUpdates, ingredientData.ingredient)
		movements = append(movements, ingredientData.movement)
	}

	return &ingredientRecipeData{
		ingredientUpdates: ingredientUpdates,
		movements:         movements,
	}, nil
}

// prepareRefundedIngredientRecipeItem prepares data for a single ingredient recipe restoration
func (p *OrderProcessorImpl) prepareRefundedIngredientRecipeItem(ctx context.Context, recipe *entities.ProductRecipe, item *entities.OrderItem, order *entities.Order, payment *entities.Payment, refundRatio float64, reason string, refundedBy uuid.UUID, refundAmount float64) (*ingredientRecipeItem, error) {
	// Calculate total ingredient quantity needed based on order item quantity
	totalIngredientQuantity := recipe.Quantity * float64(item.Quantity)

	// Get current ingredient details
	currentIngredient, err := p.ingredientRepo.GetByID(ctx, recipe.IngredientID, order.OrganizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get ingredient: %w", err)
	}

	// For ingredients, we typically don't track quantity in the ingredient entity itself
	// Instead, we create inventory movement records to track restoration
	// The ingredient entity remains unchanged, but we track the movement

	// Prepare movement record
	movement := &entities.InventoryMovement{
		OrganizationID:   order.OrganizationID,
		OutletID:         order.OutletID,
		ItemID:           recipe.IngredientID,
		ItemType:         "INGREDIENT",
		MovementType:     entities.InventoryMovementTypeRefund,
		Quantity:         totalIngredientQuantity,
		PreviousQuantity: 0, // We don't track current quantity in ingredient entity
		NewQuantity:      0, // We don't track current quantity in ingredient entity
		UnitCost:         currentIngredient.Cost,
		TotalCost:        totalIngredientQuantity * currentIngredient.Cost,
		ReferenceType: func() *entities.InventoryMovementReferenceType {
			t := entities.InventoryMovementReferenceTypeRefund
			return &t
		}(),
		ReferenceID: &payment.ID,
		OrderID:     &order.ID,
		PaymentID:   &payment.ID,
		UserID:      refundedBy,
		Reason:      stringPtr(fmt.Sprintf("Refund: %s", reason)),
		Notes:       stringPtr(fmt.Sprintf("Order: %s, Payment: %s, Refund Amount: %.2f", order.OrderNumber, payment.ID, refundAmount)),
		Metadata:    entities.Metadata{"order_item_id": item.ID, "refund_ratio": refundRatio},
	}

	return &ingredientRecipeItem{
		ingredient: currentIngredient, // Return unchanged ingredient
		movement:   movement,
	}, nil
}

// Helper function to create string pointer
func stringPtr(s string) *string {
	return &s
}
