package processor

import (
	"context"
	"fmt"

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

	"github.com/google/uuid"
)

const (
	SplitTypeItem   = "ITEM"
	SplitTypeAmount = "AMOUNT"

	MetadataKeyLastSplitPaymentID    = "last_split_payment_id"
	MetadataKeyLastSplitCustomerID   = "last_split_customer_id"
	MetadataKeyLastSplitCustomerName = "last_split_customer_name"
	MetadataKeyLastSplitAmount       = "last_split_amount"
	MetadataKeyLastSplitType         = "last_split_type"
	MetadataKeyLastSplitQuantities   = "last_split_quantities"
)



type SplitBillValidation struct {
	OrderItems       map[uuid.UUID]*entities.OrderItem
	PaidQuantities   map[uuid.UUID]int
	Outlet           *entities.Outlet
	TotalPaid        float64
	RemainingBalance float64
}

type SplitBillProcessor interface {
	SplitByAmount(ctx context.Context, req *models.SplitBillRequest, order *entities.Order, payment *entities.PaymentMethod, customer *entities.Customer) (*models.SplitBillResponse, error)
	SplitByItem(ctx context.Context, req *models.SplitBillRequest, order *entities.Order, payment *entities.PaymentMethod, customer *entities.Customer) (*models.SplitBillResponse, error)
}

type SplitBillProcessorImpl struct {
	orderRepo            OrderRepository
	orderItemRepo        OrderItemRepository
	paymentRepo          PaymentRepository
	paymentOrderItemRepo PaymentOrderItemRepository
	outletRepo           OutletRepository
}

func NewSplitBillProcessorImpl(
	orderRepo OrderRepository,
	orderItemRepo OrderItemRepository,
	paymentRepo PaymentRepository,
	paymentOrderItemRepo PaymentOrderItemRepository,
	outletRepo OutletRepository,
) *SplitBillProcessorImpl {
	return &SplitBillProcessorImpl{
		orderRepo:            orderRepo,
		orderItemRepo:        orderItemRepo,
		paymentRepo:          paymentRepo,
		paymentOrderItemRepo: paymentOrderItemRepo,
		outletRepo:           outletRepo,
	}
}

// validateSplitBillRequest validates the split bill request and returns validation data
func (p *SplitBillProcessorImpl) validateSplitBillRequest(ctx context.Context, req *models.SplitBillRequest, order *entities.Order) (*SplitBillValidation, error) {
	if req == nil {
		return nil, fmt.Errorf("split bill request cannot be nil")
	}
	if order == nil {
		return nil, fmt.Errorf("order cannot be nil")
	}
	if len(req.Items) == 0 && req.IsItem() {
		return nil, fmt.Errorf("split bill request must contain at least one item")
	}

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

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

	remainingBalance := order.TotalAmount - totalPaid

	paidQuantities, err := p.getPaidQuantitiesForOrder(ctx, req.OrderID)
	if err != nil {
		return nil, fmt.Errorf("failed to get paid quantities for order %s: %w", req.OrderID, err)
	}

	orderItems := make(map[uuid.UUID]*entities.OrderItem)
	for _, item := range req.Items {
		orderItem, err := p.orderItemRepo.GetByID(ctx, item.OrderItemID)
		if err != nil {
			return nil, fmt.Errorf("order item %s not found: %w", item.OrderItemID, err)
		}
		if orderItem.OrderID != req.OrderID {
			return nil, fmt.Errorf("order item %s does not belong to order %s", item.OrderItemID, req.OrderID)
		}

		if err := validateSplitItemQuantity(item, orderItem, paidQuantities); err != nil {
			return nil, err
		}

		orderItems[item.OrderItemID] = orderItem
	}

	return &SplitBillValidation{
		OrderItems:       orderItems,
		PaidQuantities:   paidQuantities,
		Outlet:           outlet,
		TotalPaid:        totalPaid,
		RemainingBalance: remainingBalance,
	}, nil
}

func (p *SplitBillProcessorImpl) getPaidQuantitiesForOrder(ctx context.Context, orderID uuid.UUID) (map[uuid.UUID]int, error) {
	paidQuantities, err := p.paymentOrderItemRepo.GetPaidQuantitiesByOrderID(ctx, orderID)
	if err != nil {
		return nil, fmt.Errorf("failed to get paid quantities: %w", err)
	}
	return paidQuantities, nil
}

func validateSplitItemQuantity(item models.SplitBillItemRequest, orderItem *entities.OrderItem, paidQuantities map[uuid.UUID]int) error {
	if item.Quantity <= 0 {
		return fmt.Errorf("requested quantity must be greater than 0 for order item %s", item.OrderItemID)
	}
	alreadyPaid := paidQuantities[item.OrderItemID]
	availableQuantity := orderItem.Quantity - alreadyPaid
	if item.Quantity > availableQuantity {
		return fmt.Errorf("requested quantity %d for order item %s exceeds available quantity %d (total: %d, already paid: %d)", item.Quantity, item.OrderItemID, availableQuantity, orderItem.Quantity, alreadyPaid)
	}
	return nil
}

func calculateSplitAmounts(req *models.SplitBillRequest, validation *SplitBillValidation) (float64, float64, []models.SplitBillItemResponse) {
	var totalSplitAmount float64
	var responseItems []models.SplitBillItemResponse

	for _, item := range req.Items {
		orderItem := validation.OrderItems[item.OrderItemID]
		itemAmount := float64(item.Quantity) * orderItem.UnitPrice
		itemTaxAmount := itemAmount * validation.Outlet.TaxRate
		totalItemAmount := itemAmount + itemTaxAmount

		totalSplitAmount += itemAmount

		responseItems = append(responseItems, models.SplitBillItemResponse{
			OrderItemID: item.OrderItemID,
			Amount:      totalItemAmount,
		})
	}

	splitTaxAmount := totalSplitAmount * validation.Outlet.TaxRate
	totalSplitAmountWithTax := totalSplitAmount + splitTaxAmount
	return totalSplitAmount, totalSplitAmountWithTax, responseItems
}

func (p *SplitBillProcessorImpl) createSplitPayment(ctx context.Context, req *models.SplitBillRequest, order *entities.Order, payment *entities.PaymentMethod, customer *entities.Customer, totalSplitAmountWithTax float64, existingPayments []*entities.Payment) (*entities.Payment, error) {
	splitNumber := len(existingPayments) + 1
	splitTotal := splitNumber + 1
	splitType := entities.SplitTypeItem

	splitPayment := &entities.Payment{
		OrderID:          req.OrderID,
		PaymentMethodID:  payment.ID,
		Amount:           totalSplitAmountWithTax,
		Status:           entities.PaymentTransactionStatusCompleted,
		SplitNumber:      splitNumber,
		SplitTotal:       splitTotal,
		SplitType:        &splitType,
		SplitDescription: stringPtr(fmt.Sprintf("Split payment by items for customer: %s", customer.Name)),
		Metadata: entities.Metadata{
			"split_type":    SplitTypeItem,
			"customer_id":   req.CustomerID.String(),
			"customer_name": customer.Name,
		},
	}
	if err := p.paymentRepo.Create(ctx, splitPayment); err != nil {
		return nil, fmt.Errorf("failed to create split payment: %w", err)
	}
	return splitPayment, nil
}

func (p *SplitBillProcessorImpl) createPaymentOrderItems(ctx context.Context, splitPayment *entities.Payment, req *models.SplitBillRequest, validation *SplitBillValidation) error {
	for _, item := range req.Items {
		orderItem := validation.OrderItems[item.OrderItemID]
		itemAmount := float64(item.Quantity) * orderItem.UnitPrice
		itemTaxAmount := itemAmount * validation.Outlet.TaxRate
		totalItemAmount := itemAmount + itemTaxAmount

		paymentOrderItem := &entities.PaymentOrderItem{
			PaymentID:   splitPayment.ID,
			OrderItemID: item.OrderItemID,
			Quantity:    item.Quantity,
			Amount:      totalItemAmount,
		}
		if err := p.paymentOrderItemRepo.Create(ctx, paymentOrderItem); err != nil {
			return fmt.Errorf("failed to create payment order item: %w", err)
		}

		// Update order item status to paid if fully covered
		alreadyPaid := validation.PaidQuantities[item.OrderItemID]
		newPaid := alreadyPaid + item.Quantity
		if newPaid >= orderItem.Quantity {
			if err := p.orderItemRepo.UpdateStatus(ctx, item.OrderItemID, entities.OrderItemStatusPaid); err != nil {
				return fmt.Errorf("failed to update order item status to paid: %w", err)
			}
		}
	}
	return nil
}

func (p *SplitBillProcessorImpl) updateOrderAfterSplit(ctx context.Context, order *entities.Order, req *models.SplitBillRequest, customer *entities.Customer, totalSplitAmountWithTax float64, validation *SplitBillValidation) error {
	if order.Metadata == nil {
		order.Metadata = make(entities.Metadata)
	}
	order.Metadata[MetadataKeyLastSplitPaymentID] = req.OrderID.String()
	order.Metadata[MetadataKeyLastSplitCustomerID] = req.CustomerID.String()
	order.Metadata[MetadataKeyLastSplitCustomerName] = customer.Name
	order.Metadata[MetadataKeyLastSplitAmount] = totalSplitAmountWithTax
	order.Metadata[MetadataKeyLastSplitType] = SplitTypeItem

	quantityInfo := make(map[string]interface{})
	for _, item := range req.Items {
		orderItem := validation.OrderItems[item.OrderItemID]
		quantityInfo[item.OrderItemID.String()] = map[string]interface{}{
			"quantity":     item.Quantity,
			"unit_price":   orderItem.UnitPrice,
			"total_amount": float64(item.Quantity) * orderItem.UnitPrice,
		}
	}
	order.Metadata[MetadataKeyLastSplitQuantities] = quantityInfo

	newTotalPaid := validation.TotalPaid + totalSplitAmountWithTax
	order.RemainingAmount = order.TotalAmount - newTotalPaid
	if newTotalPaid >= order.TotalAmount {
		order.PaymentStatus = entities.PaymentStatusCompleted
		order.Status = entities.OrderStatusCompleted
		order.RemainingAmount = 0
	} else {
		order.PaymentStatus = entities.PaymentStatusPartial
	}
	if err := p.orderRepo.Update(ctx, order); err != nil {
		return fmt.Errorf("failed to update order: %w", err)
	}
	return nil
}

func (p *SplitBillProcessorImpl) SplitByItem(ctx context.Context, req *models.SplitBillRequest, order *entities.Order, payment *entities.PaymentMethod, customer *entities.Customer) (*models.SplitBillResponse, error) {
	validation, err := p.validateSplitBillRequest(ctx, req, order)
	if err != nil {
		return nil, err
	}
	_, totalSplitAmountWithTax, responseItems := calculateSplitAmounts(req, validation)
	if totalSplitAmountWithTax > validation.RemainingBalance {
		return nil, fmt.Errorf("split amount %.2f cannot exceed remaining balance %.2f", totalSplitAmountWithTax, validation.RemainingBalance)
	}
	existingPayments, err := p.paymentRepo.GetByOrderID(ctx, req.OrderID)
	if err != nil {
		return nil, fmt.Errorf("failed to get existing payments: %w", err)
	}
	splitPayment, err := p.createSplitPayment(ctx, req, order, payment, customer, totalSplitAmountWithTax, existingPayments)
	if err != nil {
		return nil, err
	}
	if err := p.createPaymentOrderItems(ctx, splitPayment, req, validation); err != nil {
		return nil, err
	}
	if err := p.updateOrderAfterSplit(ctx, order, req, customer, totalSplitAmountWithTax, validation); err != nil {
		return nil, err
	}
	return &models.SplitBillResponse{
		PaymentID:  splitPayment.ID,
		OrderID:    req.OrderID,
		CustomerID: req.CustomerID,
		Type:       SplitTypeItem,
		Amount:     totalSplitAmountWithTax,
		Items:      responseItems,
		Message:    fmt.Sprintf("Successfully split payment by items (%.2f) for customer %s. Remaining balance: %.2f", totalSplitAmountWithTax, customer.Name, order.RemainingAmount),
	}, nil
}

func (p *SplitBillProcessorImpl) SplitByAmount(ctx context.Context, req *models.SplitBillRequest, order *entities.Order, payment *entities.PaymentMethod, customer *entities.Customer) (*models.SplitBillResponse, error) {
	totalPaid, err := p.paymentRepo.GetTotalPaidByOrderID(ctx, req.OrderID)
	if err != nil {
		return nil, fmt.Errorf("failed to get total paid amount: %w", err)
	}
	remainingBalance := order.TotalAmount - totalPaid
	if req.Amount > remainingBalance {
		return nil, fmt.Errorf("split amount %.2f cannot exceed remaining balance %.2f", req.Amount, remainingBalance)
	}
	existingPayments, err := p.paymentRepo.GetByOrderID(ctx, req.OrderID)
	if err != nil {
		return nil, fmt.Errorf("failed to get existing payments: %w", err)
	}
	splitNumber := len(existingPayments) + 1
	splitTotal := splitNumber + 1
	splitType := entities.SplitTypeAmount
	splitPayment := &entities.Payment{
		OrderID:          req.OrderID,
		PaymentMethodID:  payment.ID,
		Amount:           req.Amount,
		Status:           entities.PaymentTransactionStatusCompleted,
		SplitNumber:      splitNumber,
		SplitTotal:       splitTotal,
		SplitType:        &splitType,
		SplitDescription: stringPtr(fmt.Sprint("Split payment for customer")),
		Metadata: entities.Metadata{
			"split_type": SplitTypeAmount,
		},
	}
	if err := p.paymentRepo.Create(ctx, splitPayment); err != nil {
		return nil, fmt.Errorf("failed to create split payment: %w", err)
	}
	if order.Metadata == nil {
		order.Metadata = make(entities.Metadata)
	}
	order.Metadata[MetadataKeyLastSplitPaymentID] = splitPayment.ID.String()
	order.Metadata[MetadataKeyLastSplitCustomerID] = req.CustomerID.String()
	order.Metadata[MetadataKeyLastSplitAmount] = req.Amount
	order.Metadata[MetadataKeyLastSplitType] = SplitTypeAmount

	newTotalPaid := totalPaid + req.Amount
	order.RemainingAmount = order.TotalAmount - newTotalPaid
	if newTotalPaid >= order.TotalAmount {
		order.PaymentStatus = entities.PaymentStatusCompleted
		order.Status = entities.OrderStatusCompleted
		order.RemainingAmount = 0
	} else {
		order.PaymentStatus = entities.PaymentStatusPartial
	}
	if err := p.orderRepo.Update(ctx, order); err != nil {
		return nil, fmt.Errorf("failed to update order: %w", err)
	}
	return &models.SplitBillResponse{
		PaymentID:  splitPayment.ID,
		OrderID:    req.OrderID,
		CustomerID: req.CustomerID,
		Type:       SplitTypeAmount,
		Amount:     req.Amount,
		Message:    fmt.Sprintf("Successfully split payment by amount %.2f for customer %s. Remaining balance: %.2f", req.Amount, customer.Name, order.RemainingAmount),
	}, nil
}
