package processor

import (
	"apskel-pos-be/internal/entities"
	"apskel-pos-be/internal/mappers"
	"apskel-pos-be/internal/models"
	"apskel-pos-be/internal/util"
	"context"
	"fmt"
	"time"

	"github.com/google/uuid"
)

type OrderIngredientTransactionProcessor interface {
	CreateOrderIngredientTransaction(ctx context.Context, req *models.CreateOrderIngredientTransactionRequest, organizationID, outletID, createdBy uuid.UUID) (*models.OrderIngredientTransactionResponse, error)
	GetOrderIngredientTransactionByID(ctx context.Context, id, organizationID uuid.UUID) (*models.OrderIngredientTransactionResponse, error)
	UpdateOrderIngredientTransaction(ctx context.Context, id uuid.UUID, req *models.UpdateOrderIngredientTransactionRequest, organizationID uuid.UUID) (*models.OrderIngredientTransactionResponse, error)
	DeleteOrderIngredientTransaction(ctx context.Context, id, organizationID uuid.UUID) error
	ListOrderIngredientTransactions(ctx context.Context, req *models.ListOrderIngredientTransactionsRequest, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, int64, error)
	GetOrderIngredientTransactionsByOrder(ctx context.Context, orderID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error)
	GetOrderIngredientTransactionsByOrderItem(ctx context.Context, orderItemID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error)
	GetOrderIngredientTransactionsByIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error)
	GetOrderIngredientTransactionSummary(ctx context.Context, req *models.ListOrderIngredientTransactionsRequest, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionSummary, error)
	BulkCreateOrderIngredientTransactions(ctx context.Context, transactions []*models.CreateOrderIngredientTransactionRequest, organizationID, outletID, createdBy uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error)
	CalculateWasteQuantities(ctx context.Context, productID uuid.UUID, quantity float64, organizationID uuid.UUID) ([]*models.CreateOrderIngredientTransactionRequest, error)
}

type OrderIngredientTransactionProcessorImpl struct {
	orderIngredientTransactionRepo OrderIngredientTransactionRepository
	productRecipeRepo              ProductRecipeRepository
	ingredientRepo                 IngredientRepository
	unitRepo                       UnitRepository
}

func NewOrderIngredientTransactionProcessorImpl(
	orderIngredientTransactionRepo OrderIngredientTransactionRepository,
	productRecipeRepo ProductRecipeRepository,
	ingredientRepo IngredientRepository,
	unitRepo UnitRepository,
) OrderIngredientTransactionProcessor {
	return &OrderIngredientTransactionProcessorImpl{
		orderIngredientTransactionRepo: orderIngredientTransactionRepo,
		productRecipeRepo:              productRecipeRepo,
		ingredientRepo:                 ingredientRepo,
		unitRepo:                       unitRepo,
	}
}

func (p *OrderIngredientTransactionProcessorImpl) CreateOrderIngredientTransaction(ctx context.Context, req *models.CreateOrderIngredientTransactionRequest, organizationID, outletID, createdBy uuid.UUID) (*models.OrderIngredientTransactionResponse, error) {
	// Validate that gross qty >= net qty
	if req.GrossQty < req.NetQty {
		return nil, fmt.Errorf("gross quantity must be greater than or equal to net quantity")
	}

	// Validate that waste qty = gross qty - net qty
	expectedWasteQty := req.GrossQty - req.NetQty
	if req.WasteQty != expectedWasteQty {
		return nil, fmt.Errorf("waste quantity must equal gross quantity minus net quantity")
	}

	// Set transaction date if not provided
	transactionDate := time.Now()
	if req.TransactionDate != nil {
		transactionDate = *req.TransactionDate
	}

	// Create entity
	entity := &entities.OrderIngredientTransaction{
		ID:               uuid.New(),
		OrganizationID:   organizationID,
		OutletID:         &outletID,
		OrderID:          req.OrderID,
		OrderItemID:      req.OrderItemID,
		ProductID:        req.ProductID,
		ProductVariantID: req.ProductVariantID,
		IngredientID:     req.IngredientID,
		GrossQty:         req.GrossQty,
		NetQty:           req.NetQty,
		WasteQty:         req.WasteQty,
		Unit:             req.Unit,
		TransactionDate:  transactionDate,
		CreatedBy:        createdBy,
	}

	// Create in database
	if err := p.orderIngredientTransactionRepo.Create(ctx, entity); err != nil {
		return nil, fmt.Errorf("failed to create order ingredient transaction: %w", err)
	}

	// Get created entity with relations
	createdEntity, err := p.orderIngredientTransactionRepo.GetByID(ctx, entity.ID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get created order ingredient transaction: %w", err)
	}

	// Convert to response
	response := mappers.MapOrderIngredientTransactionEntityToResponse(createdEntity)
	return response, nil
}

func (p *OrderIngredientTransactionProcessorImpl) GetOrderIngredientTransactionByID(ctx context.Context, id, organizationID uuid.UUID) (*models.OrderIngredientTransactionResponse, error) {
	entity, err := p.orderIngredientTransactionRepo.GetByID(ctx, id, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get order ingredient transaction: %w", err)
	}

	response := mappers.MapOrderIngredientTransactionEntityToResponse(entity)
	return response, nil
}

func (p *OrderIngredientTransactionProcessorImpl) UpdateOrderIngredientTransaction(ctx context.Context, id uuid.UUID, req *models.UpdateOrderIngredientTransactionRequest, organizationID uuid.UUID) (*models.OrderIngredientTransactionResponse, error) {
	// Get existing entity
	entity, err := p.orderIngredientTransactionRepo.GetByID(ctx, id, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get order ingredient transaction: %w", err)
	}

	// Update fields
	if req.GrossQty != nil {
		entity.GrossQty = *req.GrossQty
	}
	if req.NetQty != nil {
		entity.NetQty = *req.NetQty
	}
	if req.WasteQty != nil {
		entity.WasteQty = *req.WasteQty
	}
	if req.Unit != nil {
		entity.Unit = *req.Unit
	}
	if req.TransactionDate != nil {
		entity.TransactionDate = *req.TransactionDate
	}

	// Validate quantities
	if entity.GrossQty < entity.NetQty {
		return nil, fmt.Errorf("gross quantity must be greater than or equal to net quantity")
	}

	expectedWasteQty := entity.GrossQty - entity.NetQty
	if entity.WasteQty != expectedWasteQty {
		return nil, fmt.Errorf("waste quantity must equal gross quantity minus net quantity")
	}

	// Update in database
	if err := p.orderIngredientTransactionRepo.Update(ctx, entity); err != nil {
		return nil, fmt.Errorf("failed to update order ingredient transaction: %w", err)
	}

	// Get updated entity with relations
	updatedEntity, err := p.orderIngredientTransactionRepo.GetByID(ctx, id, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get updated order ingredient transaction: %w", err)
	}

	response := mappers.MapOrderIngredientTransactionEntityToResponse(updatedEntity)
	return response, nil
}

func (p *OrderIngredientTransactionProcessorImpl) DeleteOrderIngredientTransaction(ctx context.Context, id, organizationID uuid.UUID) error {
	if err := p.orderIngredientTransactionRepo.Delete(ctx, id, organizationID); err != nil {
		return fmt.Errorf("failed to delete order ingredient transaction: %w", err)
	}
	return nil
}

func (p *OrderIngredientTransactionProcessorImpl) ListOrderIngredientTransactions(ctx context.Context, req *models.ListOrderIngredientTransactionsRequest, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, int64, error) {
	// Convert filters
	filters := make(map[string]interface{})
	if req.OrderID != nil {
		filters["order_id"] = *req.OrderID
	}
	if req.OrderItemID != nil {
		filters["order_item_id"] = *req.OrderItemID
	}
	if req.ProductID != nil {
		filters["product_id"] = *req.ProductID
	}
	if req.ProductVariantID != nil {
		filters["product_variant_id"] = *req.ProductVariantID
	}
	if req.IngredientID != nil {
		filters["ingredient_id"] = *req.IngredientID
	}
	if req.StartDate != nil {
		filters["start_date"] = req.StartDate.Format(time.RFC3339)
	}
	if req.EndDate != nil {
		filters["end_date"] = req.EndDate.Format(time.RFC3339)
	}

	// Set default pagination
	page := req.Page
	if page <= 0 {
		page = 1
	}
	limit := req.Limit
	if limit <= 0 {
		limit = 10
	}

	entities, total, err := p.orderIngredientTransactionRepo.List(ctx, organizationID, filters, page, limit)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to list order ingredient transactions: %w", err)
	}

	responses := mappers.MapOrderIngredientTransactionEntitiesToResponses(entities)
	return responses, total, nil
}

func (p *OrderIngredientTransactionProcessorImpl) GetOrderIngredientTransactionsByOrder(ctx context.Context, orderID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error) {
	entities, err := p.orderIngredientTransactionRepo.GetByOrderID(ctx, orderID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get order ingredient transactions by order: %w", err)
	}

	responses := mappers.MapOrderIngredientTransactionEntitiesToResponses(entities)
	return responses, nil
}

func (p *OrderIngredientTransactionProcessorImpl) GetOrderIngredientTransactionsByOrderItem(ctx context.Context, orderItemID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error) {
	entities, err := p.orderIngredientTransactionRepo.GetByOrderItemID(ctx, orderItemID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get order ingredient transactions by order item: %w", err)
	}

	responses := mappers.MapOrderIngredientTransactionEntitiesToResponses(entities)
	return responses, nil
}

func (p *OrderIngredientTransactionProcessorImpl) GetOrderIngredientTransactionsByIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error) {
	entities, err := p.orderIngredientTransactionRepo.GetByIngredientID(ctx, ingredientID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get order ingredient transactions by ingredient: %w", err)
	}

	responses := mappers.MapOrderIngredientTransactionEntitiesToResponses(entities)
	return responses, nil
}

func (p *OrderIngredientTransactionProcessorImpl) GetOrderIngredientTransactionSummary(ctx context.Context, req *models.ListOrderIngredientTransactionsRequest, organizationID uuid.UUID) ([]*models.OrderIngredientTransactionSummary, error) {
	// Convert filters
	filters := make(map[string]interface{})
	if req.OrderID != nil {
		filters["order_id"] = *req.OrderID
	}
	if req.OrderItemID != nil {
		filters["order_item_id"] = *req.OrderItemID
	}
	if req.ProductID != nil {
		filters["product_id"] = *req.ProductID
	}
	if req.ProductVariantID != nil {
		filters["product_variant_id"] = *req.ProductVariantID
	}
	if req.IngredientID != nil {
		filters["ingredient_id"] = *req.IngredientID
	}
	if req.StartDate != nil {
		filters["start_date"] = req.StartDate.Format(time.RFC3339)
	}
	if req.EndDate != nil {
		filters["end_date"] = req.EndDate.Format(time.RFC3339)
	}

	entities, err := p.orderIngredientTransactionRepo.GetSummary(ctx, organizationID, filters)
	if err != nil {
		return nil, fmt.Errorf("failed to get order ingredient transaction summary: %w", err)
	}

	summaries := mappers.MapOrderIngredientTransactionSummary(entities)
	return summaries, nil
}

func (p *OrderIngredientTransactionProcessorImpl) BulkCreateOrderIngredientTransactions(ctx context.Context, transactions []*models.CreateOrderIngredientTransactionRequest, organizationID, outletID, createdBy uuid.UUID) ([]*models.OrderIngredientTransactionResponse, error) {
	if len(transactions) == 0 {
		return []*models.OrderIngredientTransactionResponse{}, nil
	}

	// Convert to entities
	transactionEntities := make([]*entities.OrderIngredientTransaction, len(transactions))
	for i, req := range transactions {
		// Validate quantities
		if req.GrossQty < req.NetQty {
			return nil, fmt.Errorf("gross quantity must be greater than or equal to net quantity for transaction %d", i)
		}

		expectedWasteQty := req.GrossQty - req.NetQty
		if req.WasteQty != expectedWasteQty {
			return nil, fmt.Errorf("waste quantity must equal gross quantity minus net quantity for transaction %d", i)
		}

		// Set transaction date if not provided
		transactionDate := time.Now()
		if req.TransactionDate != nil {
			transactionDate = *req.TransactionDate
		}

		transactionEntities[i] = &entities.OrderIngredientTransaction{
			ID:               uuid.New(),
			OrganizationID:   organizationID,
			OutletID:         &outletID,
			OrderID:          req.OrderID,
			OrderItemID:      req.OrderItemID,
			ProductID:        req.ProductID,
			ProductVariantID: req.ProductVariantID,
			IngredientID:     req.IngredientID,
			GrossQty:         req.GrossQty,
			NetQty:           req.NetQty,
			WasteQty:         req.WasteQty,
			Unit:             req.Unit,
			TransactionDate:  transactionDate,
			CreatedBy:        createdBy,
		}
	}

	// Bulk create
	if err := p.orderIngredientTransactionRepo.BulkCreate(ctx, transactionEntities); err != nil {
		return nil, fmt.Errorf("failed to bulk create order ingredient transactions: %w", err)
	}

	// Get created entities with relations
	responses := make([]*models.OrderIngredientTransactionResponse, len(transactionEntities))
	for i, entity := range transactionEntities {
		createdEntity, err := p.orderIngredientTransactionRepo.GetByID(ctx, entity.ID, organizationID)
		if err != nil {
			return nil, fmt.Errorf("failed to get created order ingredient transaction %d: %w", i, err)
		}
		responses[i] = mappers.MapOrderIngredientTransactionEntityToResponse(createdEntity)
	}

	return responses, nil
}

func (p *OrderIngredientTransactionProcessorImpl) CalculateWasteQuantities(ctx context.Context, productID uuid.UUID, quantity float64, organizationID uuid.UUID) ([]*models.CreateOrderIngredientTransactionRequest, error) {
	// Get product recipes
	productRecipes, err := p.productRecipeRepo.GetByProductID(ctx, productID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get product recipes: %w", err)
	}

	if len(productRecipes) == 0 {
		return []*models.CreateOrderIngredientTransactionRequest{}, nil
	}

	// Get ingredient details for unit information
	ingredientMap := make(map[uuid.UUID]*entities.Ingredient)
	for _, pr := range productRecipes {
		ingredient, err := p.ingredientRepo.GetByID(ctx, pr.IngredientID, organizationID)
		if err != nil {
			return nil, fmt.Errorf("failed to get ingredient %s: %w", pr.IngredientID, err)
		}
		ingredientMap[pr.IngredientID] = ingredient
	}

	// Calculate quantities for each ingredient
	transactions := make([]*models.CreateOrderIngredientTransactionRequest, 0, len(productRecipes))
	for _, pr := range productRecipes {
		ingredient := ingredientMap[pr.IngredientID]

		// 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
		unitName := "unit" // default
		if ingredient.UnitID != uuid.Nil {
			unit, err := p.unitRepo.GetByID(ctx, ingredient.UnitID, organizationID)
			if err == nil {
				unitName = unit.Name
			}
		}

		transaction := &models.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
}
