package service

import (
	"apskel-pos-be/internal/appcontext"
	"context"
	"fmt"
	"time"

	"apskel-pos-be/internal/contract"
	"apskel-pos-be/internal/entities"
	"apskel-pos-be/internal/models"
	"apskel-pos-be/internal/processor"
	"apskel-pos-be/internal/repository"
	"apskel-pos-be/internal/util"

	"github.com/google/uuid"
)

type OrderService 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 OrderServiceImpl struct {
	orderProcessor                      processor.OrderProcessor
	tableRepo                           repository.TableRepositoryInterface
	orderIngredientTransactionService   *OrderIngredientTransactionService
	orderIngredientTransactionProcessor processor.OrderIngredientTransactionProcessor
	productRecipeRepo                   repository.ProductRecipeRepository
	txManager                           *repository.TxManager
}

func NewOrderServiceImpl(orderProcessor processor.OrderProcessor, tableRepo repository.TableRepositoryInterface, orderIngredientTransactionService *OrderIngredientTransactionService, orderIngredientTransactionProcessor processor.OrderIngredientTransactionProcessor, productRecipeRepo repository.ProductRecipeRepository, txManager *repository.TxManager) *OrderServiceImpl {
	return &OrderServiceImpl{
		orderProcessor:                      orderProcessor,
		tableRepo:                           tableRepo,
		orderIngredientTransactionService:   orderIngredientTransactionService,
		orderIngredientTransactionProcessor: orderIngredientTransactionProcessor,
		productRecipeRepo:                   productRecipeRepo,
		txManager:                           txManager,
	}
}

func (s *OrderServiceImpl) CreateOrder(ctx context.Context, req *models.CreateOrderRequest, organizationID uuid.UUID) (*models.OrderResponse, error) {
	if err := s.validateCreateOrderRequest(req, organizationID); err != nil {
		return nil, fmt.Errorf("validation error: %w", err)
	}

	if req.TableID != nil {
		if err := s.validateTable(ctx, req); err != nil {
			return nil, fmt.Errorf("table validation failed: %w", err)
		}
	}

	var response *models.OrderResponse
	var ingredientTransactions []*contract.CreateOrderIngredientTransactionRequest

	// Use transaction to ensure atomicity
	err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
		// Create the order
		orderResp, err := s.orderProcessor.CreateOrder(txCtx, req, organizationID)
		if err != nil {
			return fmt.Errorf("failed to create order: %w", err)
		}
		response = orderResp

		// Create ingredient transactions for each order item
		ingredientTransactions, err = s.createIngredientTransactions(txCtx, response.ID, response.OrderItems)
		if err != nil {
			return fmt.Errorf("failed to create ingredient transactions: %w", err)
		}

		// Bulk create ingredient transactions
		if len(ingredientTransactions) > 0 {
			_, err = s.orderIngredientTransactionService.BulkCreateOrderIngredientTransactions(txCtx, ingredientTransactions)
			if err != nil {
				return fmt.Errorf("failed to bulk create ingredient transactions: %w", err)
			}
		}

		// Occupy table if specified
		if req.TableID != nil {
			if err := s.occupyTableWithOrder(txCtx, *req.TableID, response.ID); err != nil {
				// Log warning but don't fail the transaction
				fmt.Printf("Warning: failed to occupy table %s with order %s: %v\n", *req.TableID, response.ID, err)
			}
		}

		return nil
	})

	if err != nil {
		return nil, err
	}

	return response, nil
}

// createIngredientTransactions creates ingredient transactions for order items efficiently
func (s *OrderServiceImpl) createIngredientTransactions(ctx context.Context, orderID uuid.UUID, orderItems []models.OrderItemResponse) ([]*contract.CreateOrderIngredientTransactionRequest, error) {
	appCtx := appcontext.FromGinContext(ctx)
	organizationID := appCtx.OrganizationID
	var allTransactions []*contract.CreateOrderIngredientTransactionRequest

	for _, orderItem := range orderItems {
		// Get product recipes for this product
		productRecipes, err := s.productRecipeRepo.GetByProductID(ctx, orderItem.ProductID, organizationID)
		if err != nil {
			return nil, fmt.Errorf("failed to get product recipes for product %s: %w", orderItem.ProductID, err)
		}

		if len(productRecipes) == 0 {
			continue // Skip if no recipes
		}

		// Calculate waste quantities
		transactions, err := s.calculateWasteQuantities(productRecipes, float64(orderItem.Quantity))
		if err != nil {
			return nil, fmt.Errorf("failed to calculate waste quantities for product %s: %w", err)
		}

		// Set common fields for all transactions
		for _, transaction := range transactions {
			transaction.OrderID = orderID
			transaction.OrderItemID = &orderItem.ID
			transaction.ProductID = orderItem.ProductID
			transaction.ProductVariantID = orderItem.ProductVariantID
		}

		allTransactions = append(allTransactions, transactions...)
	}

	return allTransactions, nil
}

func (s *OrderServiceImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error) {
	if orderID == uuid.Nil {
		return nil, fmt.Errorf("invalid order ID")
	}

	if err := s.validateAddToOrderRequest(req); err != nil {
		return nil, fmt.Errorf("validation error: %w", err)
	}

	var response *models.AddToOrderResponse
	var ingredientTransactions []*contract.CreateOrderIngredientTransactionRequest

	err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
		addResp, err := s.orderProcessor.AddToOrder(txCtx, orderID, req)
		if err != nil {
			return fmt.Errorf("failed to add items to order: %w", err)
		}
		response = addResp

		ingredientTransactions, err = s.createIngredientTransactions(txCtx, orderID, response.AddedItems)
		if err != nil {
			return fmt.Errorf("failed to create ingredient transactions: %w", err)
		}

		if len(ingredientTransactions) > 0 {
			_, err = s.orderIngredientTransactionService.BulkCreateOrderIngredientTransactions(txCtx, ingredientTransactions)
			if err != nil {
				return fmt.Errorf("failed to bulk create ingredient transactions: %w", err)
			}
		}

		return nil
	})

	if err != nil {
		return nil, err
	}

	return response, nil
}

func (s *OrderServiceImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error) {
	if err := s.validateUpdateOrderRequest(req); err != nil {
		return nil, fmt.Errorf("validation error: %w", err)
	}

	response, err := s.orderProcessor.UpdateOrder(ctx, id, req)
	if err != nil {
		return nil, fmt.Errorf("failed to update order: %w", err)
	}

	return response, nil
}

func (s *OrderServiceImpl) GetOrderByID(ctx context.Context, id uuid.UUID) (*models.OrderResponse, error) {
	if id == uuid.Nil {
		return nil, fmt.Errorf("invalid order ID")
	}

	response, err := s.orderProcessor.GetOrderByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("failed to get order: %w", err)
	}

	return response, nil
}

func (s *OrderServiceImpl) ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error) {
	if err := s.validateListOrdersRequest(req); err != nil {
		return nil, fmt.Errorf("validation error: %w", err)
	}

	response, err := s.orderProcessor.ListOrders(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("failed to list orders: %w", err)
	}

	return response, nil
}

func (s *OrderServiceImpl) VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID) error {
	if req.OrderID == uuid.Nil {
		return fmt.Errorf("invalid order ID")
	}

	if voidedBy == uuid.Nil {
		return fmt.Errorf("invalid user ID")
	}

	if err := s.orderProcessor.VoidOrder(ctx, req, voidedBy); err != nil {
		return fmt.Errorf("failed to void order: %w", err)
	}

	if err := s.handleTableReleaseOnVoid(ctx, req.OrderID); err != nil {
		fmt.Printf("Warning: failed to handle table release for voided order %s: %v\n", req.OrderID, err)
	}

	return nil
}

func (s *OrderServiceImpl) RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID) error {
	// Validate inputs
	if id == uuid.Nil {
		return fmt.Errorf("invalid order ID")
	}
	if refundedBy == uuid.Nil {
		return fmt.Errorf("invalid user ID")
	}

	// Validate refund request
	if err := s.validateRefundOrderRequest(req); err != nil {
		return fmt.Errorf("validation error: %w", err)
	}

	// Process order refund
	if err := s.orderProcessor.RefundOrder(ctx, id, req, refundedBy); err != nil {
		return fmt.Errorf("failed to refund order: %w", err)
	}

	return nil
}

func (s *OrderServiceImpl) CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error) {
	if err := s.validateCreatePaymentRequest(req); err != nil {
		return nil, fmt.Errorf("validation error: %w", err)
	}

	response, err := s.orderProcessor.CreatePayment(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("failed to create payment: %w", err)
	}

	if err := s.handleTableReleaseOnPayment(ctx, req.OrderID); err != nil {
		fmt.Printf("Warning: failed to handle table release for order %s: %v\n", req.OrderID, err)
	}

	return response, nil
}

func (s *OrderServiceImpl) RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error {
	// Validate inputs
	if paymentID == uuid.Nil {
		return fmt.Errorf("invalid payment ID")
	}
	if refundAmount <= 0 {
		return fmt.Errorf("refund amount must be greater than zero")
	}
	if refundedBy == uuid.Nil {
		return fmt.Errorf("invalid user ID")
	}

	// Process payment refund
	if err := s.orderProcessor.RefundPayment(ctx, paymentID, refundAmount, reason, refundedBy); err != nil {
		return fmt.Errorf("failed to refund payment: %w", err)
	}

	return nil
}

func (s *OrderServiceImpl) SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error) {
	// Validate inputs
	if orderID == uuid.Nil {
		return nil, fmt.Errorf("invalid order ID")
	}

	if organizationID == uuid.Nil {
		return nil, fmt.Errorf("invalid organization ID")
	}

	// Validate request
	if err := s.validateSetOrderCustomerRequest(req); err != nil {
		return nil, fmt.Errorf("validation error: %w", err)
	}

	// Process setting customer for order
	response, err := s.orderProcessor.SetOrderCustomer(ctx, orderID, req, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to set customer for order: %w", err)
	}

	return response, nil
}

func (s *OrderServiceImpl) validateAddToOrderRequest(req *models.AddToOrderRequest) error {
	if req == nil {
		return fmt.Errorf("request cannot be nil")
	}

	if len(req.OrderItems) == 0 {
		return fmt.Errorf("must add at least one item")
	}

	for i, item := range req.OrderItems {
		if item.ProductID == uuid.Nil {
			return fmt.Errorf("product ID is required for item %d", i+1)
		}

		if item.Quantity <= 0 {
			return fmt.Errorf("quantity must be greater than zero for item %d", i+1)
		}

		if item.UnitPrice != nil && *item.UnitPrice < 0 {
			return fmt.Errorf("unit price cannot be negative for item %d", i+1)
		}
	}

	return nil
}

func (s *OrderServiceImpl) validateCreateOrderRequest(req *models.CreateOrderRequest, organizationID uuid.UUID) error {
	if req == nil {
		return fmt.Errorf("request cannot be nil")
	}

	if organizationID == uuid.Nil {
		return fmt.Errorf("organization ID is required")
	}

	if req.OutletID == uuid.Nil {
		return fmt.Errorf("outlet ID is required")
	}

	if req.UserID == uuid.Nil {
		return fmt.Errorf("user ID is required")
	}

	// Validate table ID if provided
	if req.TableID != nil && *req.TableID == uuid.Nil {
		return fmt.Errorf("table ID cannot be nil if provided")
	}

	if len(req.OrderItems) == 0 {
		return fmt.Errorf("order must have at least one item")
	}

	for i, item := range req.OrderItems {
		if item.ProductID == uuid.Nil {
			return fmt.Errorf("product ID is required for item %d", i+1)
		}

		if item.Quantity <= 0 {
			return fmt.Errorf("quantity must be greater than zero for item %d", i+1)
		}

		if item.UnitPrice != nil && *item.UnitPrice < 0 {
			return fmt.Errorf("unit price cannot be negative for item %d", i+1)
		}
	}

	return nil
}

func (s *OrderServiceImpl) validateUpdateOrderRequest(req *models.UpdateOrderRequest) error {
	if req == nil {
		return fmt.Errorf("request cannot be nil")
	}

	if req.DiscountAmount != nil && *req.DiscountAmount < 0 {
		return fmt.Errorf("discount amount cannot be negative")
	}

	return nil
}

func (s *OrderServiceImpl) validateListOrdersRequest(req *models.ListOrdersRequest) error {
	if req == nil {
		return fmt.Errorf("request cannot be nil")
	}

	if req.Page < 1 {
		return fmt.Errorf("page must be greater than zero")
	}

	if req.Limit < 1 || req.Limit > 100 {
		return fmt.Errorf("limit must be between 1 and 100")
	}

	return nil
}

func (s *OrderServiceImpl) validateRefundOrderRequest(req *models.RefundOrderRequest) error {
	if req == nil {
		return fmt.Errorf("request cannot be nil")
	}

	// Must have either refund amount or order items
	if req.RefundAmount == nil && len(req.OrderItems) == 0 {
		return fmt.Errorf("must specify either refund amount or order items to refund")
	}

	// Cannot have both refund amount and order items
	if req.RefundAmount != nil && len(req.OrderItems) > 0 {
		return fmt.Errorf("cannot specify both refund amount and order items")
	}

	if req.RefundAmount != nil && *req.RefundAmount <= 0 {
		return fmt.Errorf("refund amount must be greater than zero")
	}

	// Validate order items if provided
	for i, item := range req.OrderItems {
		if item.OrderItemID == uuid.Nil {
			return fmt.Errorf("order item ID is required for item %d", i+1)
		}

		if item.RefundQuantity < 0 {
			return fmt.Errorf("refund quantity cannot be negative for item %d", i+1)
		}

		if item.RefundAmount != nil && *item.RefundAmount < 0 {
			return fmt.Errorf("refund amount cannot be negative for item %d", i+1)
		}
	}

	return nil
}

func (s *OrderServiceImpl) validateCreatePaymentRequest(req *models.CreatePaymentRequest) error {
	if req == nil {
		return fmt.Errorf("request cannot be nil")
	}

	if req.OrderID == uuid.Nil {
		return fmt.Errorf("order ID is required")
	}

	if req.PaymentMethodID == uuid.Nil {
		return fmt.Errorf("payment method ID is required")
	}

	if req.Amount <= 0 {
		return fmt.Errorf("payment amount must be greater than zero")
	}

	if len(req.PaymentOrderItems) > 0 {
		totalItemAmount := float64(0)
		for i, item := range req.PaymentOrderItems {
			if item.OrderItemID == uuid.Nil {
				return fmt.Errorf("order item ID is required for payment item %d", i+1)
			}

			if item.Amount <= 0 {
				return fmt.Errorf("payment item amount must be greater than zero for item %d", i+1)
			}

			totalItemAmount += item.Amount
		}

		if totalItemAmount != req.Amount {
			return fmt.Errorf("sum of payment item amounts must equal total payment amount")
		}
	}

	return nil
}

func (s *OrderServiceImpl) validateSetOrderCustomerRequest(req *models.SetOrderCustomerRequest) error {
	if req == nil {
		return fmt.Errorf("request cannot be nil")
	}

	if req.CustomerID == uuid.Nil {
		return fmt.Errorf("customer ID is required")
	}

	return nil
}

func (s *OrderServiceImpl) SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error) {
	if err := s.validateSplitBillRequest(req); err != nil {
		return nil, fmt.Errorf("validation error: %w", err)
	}

	response, err := s.orderProcessor.SplitBill(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("failed to split bill: %w", err)
	}

	if err := s.handleTableReleaseOnPayment(ctx, req.OrderID); err != nil {
		fmt.Printf("Warning: failed to handle table release for order %s after split bill: %v\n", req.OrderID, err)
	}

	return response, nil
}

func (s *OrderServiceImpl) validateSplitBillRequest(req *models.SplitBillRequest) error {
	if req == nil {
		return fmt.Errorf("request cannot be nil")
	}

	if req.OrderID == uuid.Nil {
		return fmt.Errorf("order ID is required")
	}

	if req.PaymentMethodID == uuid.Nil {
		return fmt.Errorf("payment ID is required")
	}

	if req.Type != "ITEM" && req.Type != "AMOUNT" {
		return fmt.Errorf("split type must be either ITEM or AMOUNT")
	}

	if req.Type == "ITEM" {
		if len(req.Items) == 0 {
			return fmt.Errorf("items are required when splitting by ITEM")
		}

		totalItemAmount := 0
		for i, item := range req.Items {
			if item.OrderItemID == uuid.Nil {
				return fmt.Errorf("order item ID is required for item %d", i+1)
			}

			if item.Quantity <= 0 {
				return fmt.Errorf("quantity must be greater than zero for item %d", i+1)
			}

			totalItemAmount += item.Quantity
		}

		if totalItemAmount <= 0 {
			return fmt.Errorf("total item amount must be greater than zero")
		}
	}

	if req.Type == "AMOUNT" {
		if req.Amount <= 0 {
			return fmt.Errorf("amount must be greater than zero when splitting by AMOUNT")
		}
	}

	return nil
}

// validateTable validates that the table exists and is available for occupation
func (s *OrderServiceImpl) validateTable(ctx context.Context, req *models.CreateOrderRequest) error {
	// Validate table exists and is available
	table, err := s.tableRepo.GetByID(ctx, *req.TableID)
	if err != nil {
		return fmt.Errorf("table not found: %w", err)
	}

	// Check if table belongs to the same outlet
	if table.OutletID != req.OutletID {
		return fmt.Errorf("table does not belong to the specified outlet")
	}

	// Check if table is available for occupation
	if !table.CanBeOccupied() {
		return fmt.Errorf("table is not available for occupation (current status: %s)", table.Status)
	}

	return nil
}

func (s *OrderServiceImpl) occupyTableWithOrder(ctx context.Context, tableID, orderID uuid.UUID) error {
	startTime := time.Now()
	if err := s.tableRepo.OccupyTable(ctx, tableID, orderID, &startTime); err != nil {
		return fmt.Errorf("failed to occupy table: %w", err)
	}
	return nil
}

func (s *OrderServiceImpl) handleTableReleaseOnPayment(ctx context.Context, orderID uuid.UUID) error {
	order, err := s.orderProcessor.GetOrderByID(ctx, orderID)
	if err != nil {
		return fmt.Errorf("failed to get order: %w", err)
	}

	if order.PaymentStatus == "completed" {
		table, err := s.tableRepo.GetByOrderID(ctx, orderID)
		if err != nil {
			return nil
		}

		if table != nil {
			if err := s.tableRepo.ReleaseTable(ctx, table.ID, order.TotalAmount); err != nil {
				return fmt.Errorf("failed to release table: %w", err)
			}
		}
	}

	return nil
}

// handleTableReleaseOnVoid releases the table when an order is voided
func (s *OrderServiceImpl) handleTableReleaseOnVoid(ctx context.Context, orderID uuid.UUID) error {
	table, err := s.tableRepo.GetByOrderID(ctx, orderID)
	if err != nil {
		// Table might not exist or not be occupied, which is fine
		return nil
	}

	if table != nil {
		if err := s.tableRepo.ReleaseTable(ctx, table.ID, 0); err != nil {
			return fmt.Errorf("failed to release table: %w", err)
		}
	}

	return nil
}

func (s *OrderServiceImpl) createOrderIngredientTransactions(ctx context.Context, order *models.Order, orderItems []*models.OrderItem) error {
	for _, orderItem := range orderItems {
		productRecipes, err := s.productRecipeRepo.GetByProductID(ctx, orderItem.ProductID, order.OrganizationID)
		if err != nil {
			return fmt.Errorf("failed to get product recipes for product %s: %w", orderItem.ProductID, err)
		}

		if len(productRecipes) == 0 {
			continue // Skip if no recipes
		}

		// Calculate waste quantities using the utility function
		transactions, err := s.calculateWasteQuantities(productRecipes, float64(orderItem.Quantity))
		if err != nil {
			return fmt.Errorf("failed to calculate waste quantities for product %s: %w", orderItem.ProductID, err)
		}

		// Set common fields for all transactions
		for _, transaction := range transactions {
			transaction.OrderID = order.ID
			transaction.OrderItemID = &orderItem.ID
			transaction.ProductID = orderItem.ProductID
			transaction.ProductVariantID = orderItem.ProductVariantID
		}

		// Bulk create transactions
		if len(transactions) > 0 {
			_, err := s.orderIngredientTransactionService.BulkCreateOrderIngredientTransactions(ctx, transactions)
			if err != nil {
				return fmt.Errorf("failed to create order ingredient transactions for product %s: %w", orderItem.ProductID, err)
			}
		}
	}

	return nil
}

// calculateWasteQuantities calculates gross, net, and waste quantities for product recipes
func (s *OrderServiceImpl) calculateWasteQuantities(productRecipes []*entities.ProductRecipe, quantity float64) ([]*contract.CreateOrderIngredientTransactionRequest, error) {
	if len(productRecipes) == 0 {
		return []*contract.CreateOrderIngredientTransactionRequest{}, nil
	}

	transactions := make([]*contract.CreateOrderIngredientTransactionRequest, 0, len(productRecipes))

	for _, pr := range productRecipes {
		// Calculate net quantity (actual quantity needed for the product)
		netQty := pr.Quantity * quantity

		// Calculate gross quantity (including waste)
		wasteMultiplier := 1 + (pr.WastePercentage / 100)
		grossQty := netQty * wasteMultiplier

		// Calculate waste quantity
		wasteQty := grossQty - netQty

		// Get unit name from ingredient
		unitName := "unit" // default
		if pr.Ingredient != nil && pr.Ingredient.Unit != nil {
			unitName = pr.Ingredient.Unit.Name
		}

		transaction := &contract.CreateOrderIngredientTransactionRequest{
			IngredientID: pr.IngredientID,
			GrossQty:     util.RoundToDecimalPlaces(grossQty, 3),
			NetQty:       util.RoundToDecimalPlaces(netQty, 3),
			WasteQty:     util.RoundToDecimalPlaces(wasteQty, 3),
			Unit:         unitName,
		}

		transactions = append(transactions, transaction)
	}

	return transactions, nil
}
