package processor

import (
	"context"
	"fmt"

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

	"github.com/google/uuid"
)

type PurchaseOrderProcessor interface {
	CreatePurchaseOrder(ctx context.Context, organizationID uuid.UUID, req *models.CreatePurchaseOrderRequest) (*models.PurchaseOrderResponse, error)
	UpdatePurchaseOrder(ctx context.Context, id, organizationID uuid.UUID, req *models.UpdatePurchaseOrderRequest) (*models.PurchaseOrderResponse, error)
	DeletePurchaseOrder(ctx context.Context, id, organizationID uuid.UUID) error
	GetPurchaseOrderByID(ctx context.Context, id, organizationID uuid.UUID) (*models.PurchaseOrderResponse, error)
	ListPurchaseOrders(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.PurchaseOrderResponse, int, error)
	GetPurchaseOrdersByStatus(ctx context.Context, organizationID uuid.UUID, status string) ([]*models.PurchaseOrderResponse, error)
	GetOverduePurchaseOrders(ctx context.Context, organizationID uuid.UUID) ([]*models.PurchaseOrderResponse, error)
	UpdatePurchaseOrderStatus(ctx context.Context, id, organizationID, userID, outletID uuid.UUID, status string) (*models.PurchaseOrderResponse, error)
}

type PurchaseOrderProcessorImpl struct {
	purchaseOrderRepo        PurchaseOrderRepository
	vendorRepo               VendorRepository
	ingredientRepo           IngredientRepository
	unitRepo                 UnitRepository
	fileRepo                 FileRepository
	inventoryMovementService InventoryMovementService
	unitConverterRepo        IngredientUnitConverterRepository
}

func NewPurchaseOrderProcessorImpl(
	purchaseOrderRepo PurchaseOrderRepository,
	vendorRepo VendorRepository,
	ingredientRepo IngredientRepository,
	unitRepo UnitRepository,
	fileRepo FileRepository,
	inventoryMovementService InventoryMovementService,
	unitConverterRepo IngredientUnitConverterRepository,
) *PurchaseOrderProcessorImpl {
	return &PurchaseOrderProcessorImpl{
		purchaseOrderRepo:        purchaseOrderRepo,
		vendorRepo:               vendorRepo,
		ingredientRepo:           ingredientRepo,
		unitRepo:                 unitRepo,
		fileRepo:                 fileRepo,
		inventoryMovementService: inventoryMovementService,
		unitConverterRepo:        unitConverterRepo,
	}
}

func (p *PurchaseOrderProcessorImpl) CreatePurchaseOrder(ctx context.Context, organizationID uuid.UUID, req *models.CreatePurchaseOrderRequest) (*models.PurchaseOrderResponse, error) {
	// Check if vendor exists and belongs to organization
	_, err := p.vendorRepo.GetByIDAndOrganizationID(ctx, req.VendorID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("vendor not found: %w", err)
	}

	// Check if PO number already exists in organization
	existingPO, err := p.purchaseOrderRepo.GetByPONumber(ctx, req.PONumber, organizationID)
	if err == nil && existingPO != nil {
		return nil, fmt.Errorf("purchase order with PO number %s already exists in this organization", req.PONumber)
	}

	// Validate ingredients and units exist
	for i, item := range req.Items {
		_, err := p.ingredientRepo.GetByID(ctx, item.IngredientID, organizationID)
		if err != nil {
			return nil, fmt.Errorf("ingredient not found for item %d: %w", i, err)
		}

		_, err = p.unitRepo.GetByID(ctx, item.UnitID, organizationID)
		if err != nil {
			return nil, fmt.Errorf("unit not found for item %d: %w", i, err)
		}
	}

	// Calculate total amount
	totalAmount := 0.0
	for _, item := range req.Items {
		totalAmount += item.Amount
	}

	// Create purchase order entity
	poEntity := &entities.PurchaseOrder{
		OrganizationID:  organizationID,
		VendorID:        req.VendorID,
		PONumber:        req.PONumber,
		TransactionDate: req.TransactionDate,
		DueDate:         req.DueDate,
		Reference:       req.Reference,
		Status:          "draft", // Default status
		Message:         req.Message,
		TotalAmount:     totalAmount,
	}

	if req.Status != nil {
		poEntity.Status = *req.Status
	}

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

	// Create purchase order items
	for _, itemReq := range req.Items {
		itemEntity := &entities.PurchaseOrderItem{
			PurchaseOrderID: poEntity.ID,
			IngredientID:    itemReq.IngredientID,
			Description:     itemReq.Description,
			Quantity:        itemReq.Quantity,
			UnitID:          itemReq.UnitID,
			Amount:          itemReq.Amount,
		}

		err = p.purchaseOrderRepo.CreateItem(ctx, itemEntity)
		if err != nil {
			return nil, fmt.Errorf("failed to create purchase order item: %w", err)
		}
	}

	// Create attachments if provided
	for _, fileID := range req.AttachmentFileIDs {
		attachmentEntity := &entities.PurchaseOrderAttachment{
			PurchaseOrderID: poEntity.ID,
			FileID:          fileID,
		}

		err = p.purchaseOrderRepo.CreateAttachment(ctx, attachmentEntity)
		if err != nil {
			return nil, fmt.Errorf("failed to create purchase order attachment: %w", err)
		}
	}

	// Get the created purchase order with all relations
	createdPO, err := p.purchaseOrderRepo.GetByID(ctx, poEntity.ID)
	if err != nil {
		return nil, fmt.Errorf("failed to get created purchase order: %w", err)
	}

	return mappers.PurchaseOrderEntityToResponse(createdPO), nil
}

func (p *PurchaseOrderProcessorImpl) UpdatePurchaseOrder(ctx context.Context, id, organizationID uuid.UUID, req *models.UpdatePurchaseOrderRequest) (*models.PurchaseOrderResponse, error) {
	// Get existing purchase order
	poEntity, err := p.purchaseOrderRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
	if err != nil {
		return nil, fmt.Errorf("purchase order not found: %w", err)
	}

	// Check if vendor exists and belongs to organization (if vendor is being updated)
	if req.VendorID != nil {
		_, err := p.vendorRepo.GetByIDAndOrganizationID(ctx, *req.VendorID, organizationID)
		if err != nil {
			return nil, fmt.Errorf("vendor not found: %w", err)
		}
		poEntity.VendorID = *req.VendorID
	}

	// Check if PO number already exists (if PO number is being updated)
	if req.PONumber != nil && *req.PONumber != poEntity.PONumber {
		existingPO, err := p.purchaseOrderRepo.GetByPONumber(ctx, *req.PONumber, organizationID)
		if err == nil && existingPO != nil {
			return nil, fmt.Errorf("purchase order with PO number %s already exists in this organization", *req.PONumber)
		}
		poEntity.PONumber = *req.PONumber
	}

	// Update other fields
	if req.TransactionDate != nil {
		poEntity.TransactionDate = *req.TransactionDate
	}
	if req.DueDate != nil {
		poEntity.DueDate = *req.DueDate
	}
	if req.Reference != nil {
		poEntity.Reference = req.Reference
	}
	if req.Status != nil {
		poEntity.Status = *req.Status
	}
	if req.Message != nil {
		poEntity.Message = req.Message
	}

	// Update items if provided
	if req.Items != nil {
		// Delete existing items
		err = p.purchaseOrderRepo.DeleteItemsByPurchaseOrderID(ctx, poEntity.ID)
		if err != nil {
			return nil, fmt.Errorf("failed to delete existing items: %w", err)
		}

		// Create new items
		totalAmount := 0.0
		for _, itemReq := range req.Items {
			// Validate ingredients and units exist
			if itemReq.IngredientID != nil {
				_, err := p.ingredientRepo.GetByID(ctx, *itemReq.IngredientID, organizationID)
				if err != nil {
					return nil, fmt.Errorf("ingredient not found: %w", err)
				}
			}

			if itemReq.UnitID != nil {
				_, err := p.unitRepo.GetByID(ctx, *itemReq.UnitID, organizationID)
				if err != nil {
					return nil, fmt.Errorf("unit not found: %w", err)
				}
			}

			// Use existing values if not provided
			ingredientID := poEntity.Items[0].IngredientID // This is a simplified approach
			unitID := poEntity.Items[0].UnitID
			quantity := poEntity.Items[0].Quantity
			amount := poEntity.Items[0].Amount
			description := poEntity.Items[0].Description

			if itemReq.IngredientID != nil {
				ingredientID = *itemReq.IngredientID
			}
			if itemReq.UnitID != nil {
				unitID = *itemReq.UnitID
			}
			if itemReq.Quantity != nil {
				quantity = *itemReq.Quantity
			}
			if itemReq.Amount != nil {
				amount = *itemReq.Amount
			}
			if itemReq.Description != nil {
				description = itemReq.Description
			}

			itemEntity := &entities.PurchaseOrderItem{
				PurchaseOrderID: poEntity.ID,
				IngredientID:    ingredientID,
				Description:     description,
				Quantity:        quantity,
				UnitID:          unitID,
				Amount:          amount,
			}

			err = p.purchaseOrderRepo.CreateItem(ctx, itemEntity)
			if err != nil {
				return nil, fmt.Errorf("failed to create purchase order item: %w", err)
			}

			totalAmount += amount
		}

		poEntity.TotalAmount = totalAmount
	}

	// Update attachments if provided
	if req.AttachmentFileIDs != nil {
		// Delete existing attachments
		err = p.purchaseOrderRepo.DeleteAttachmentsByPurchaseOrderID(ctx, poEntity.ID)
		if err != nil {
			return nil, fmt.Errorf("failed to delete existing attachments: %w", err)
		}

		// Create new attachments
		for _, fileID := range req.AttachmentFileIDs {
			attachmentEntity := &entities.PurchaseOrderAttachment{
				PurchaseOrderID: poEntity.ID,
				FileID:          fileID,
			}

			err = p.purchaseOrderRepo.CreateAttachment(ctx, attachmentEntity)
			if err != nil {
				return nil, fmt.Errorf("failed to create purchase order attachment: %w", err)
			}
		}
	}

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

	// Get the updated purchase order with all relations
	updatedPO, err := p.purchaseOrderRepo.GetByID(ctx, poEntity.ID)
	if err != nil {
		return nil, fmt.Errorf("failed to get updated purchase order: %w", err)
	}

	return mappers.PurchaseOrderEntityToResponse(updatedPO), nil
}

func (p *PurchaseOrderProcessorImpl) DeletePurchaseOrder(ctx context.Context, id, organizationID uuid.UUID) error {
	_, err := p.purchaseOrderRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
	if err != nil {
		return fmt.Errorf("purchase order not found: %w", err)
	}

	err = p.purchaseOrderRepo.Delete(ctx, id)
	if err != nil {
		return fmt.Errorf("failed to delete purchase order: %w", err)
	}

	return nil
}

func (p *PurchaseOrderProcessorImpl) GetPurchaseOrderByID(ctx context.Context, id, organizationID uuid.UUID) (*models.PurchaseOrderResponse, error) {
	poEntity, err := p.purchaseOrderRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
	if err != nil {
		return nil, fmt.Errorf("purchase order not found: %w", err)
	}

	return mappers.PurchaseOrderEntityToResponse(poEntity), nil
}

func (p *PurchaseOrderProcessorImpl) ListPurchaseOrders(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.PurchaseOrderResponse, int, error) {
	offset := (page - 1) * limit
	poEntities, total, err := p.purchaseOrderRepo.List(ctx, organizationID, filters, limit, offset)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to list purchase orders: %w", err)
	}

	poResponses := make([]*models.PurchaseOrderResponse, len(poEntities))
	for i, poEntity := range poEntities {
		poResponses[i] = mappers.PurchaseOrderEntityToResponse(poEntity)
	}

	totalPages := int((total + int64(limit) - 1) / int64(limit))
	return poResponses, totalPages, nil
}

func (p *PurchaseOrderProcessorImpl) GetPurchaseOrdersByStatus(ctx context.Context, organizationID uuid.UUID, status string) ([]*models.PurchaseOrderResponse, error) {
	poEntities, err := p.purchaseOrderRepo.GetByStatus(ctx, organizationID, status)
	if err != nil {
		return nil, fmt.Errorf("failed to get purchase orders by status: %w", err)
	}

	poResponses := make([]*models.PurchaseOrderResponse, len(poEntities))
	for i, poEntity := range poEntities {
		poResponses[i] = mappers.PurchaseOrderEntityToResponse(poEntity)
	}

	return poResponses, nil
}

func (p *PurchaseOrderProcessorImpl) GetOverduePurchaseOrders(ctx context.Context, organizationID uuid.UUID) ([]*models.PurchaseOrderResponse, error) {
	poEntities, err := p.purchaseOrderRepo.GetOverdue(ctx, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get overdue purchase orders: %w", err)
	}

	poResponses := make([]*models.PurchaseOrderResponse, len(poEntities))
	for i, poEntity := range poEntities {
		poResponses[i] = mappers.PurchaseOrderEntityToResponse(poEntity)
	}

	return poResponses, nil
}

func (p *PurchaseOrderProcessorImpl) UpdatePurchaseOrderStatus(ctx context.Context, id, organizationID, userID, outletID uuid.UUID, status string) (*models.PurchaseOrderResponse, error) {
	// Get the purchase order with items to check current status
	po, err := p.purchaseOrderRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
	if err != nil {
		return nil, fmt.Errorf("purchase order not found: %w", err)
	}

	// Check if status is changing to "received" and current status is not "received"
	if status == "received" && po.Status != "received" {
		// Get purchase order with items for inventory update
		poWithItems, err := p.purchaseOrderRepo.GetByID(ctx, id)
		if err != nil {
			return nil, fmt.Errorf("failed to get purchase order with items: %w", err)
		}

		// Update inventory for each item
		for _, item := range poWithItems.Items {
			// Get ingredient to find its base unit
			ingredient, err := p.ingredientRepo.GetByID(ctx, item.IngredientID, organizationID)
			if err != nil {
				return nil, fmt.Errorf("failed to get ingredient %s: %w", item.IngredientID, err)
			}

			// Convert quantity to ingredient's base unit if needed
			quantityToAdd := item.Quantity
			if item.UnitID != ingredient.UnitID {
				// Convert from purchase unit to ingredient's base unit
				convertedQuantity, err := p.unitConverterRepo.ConvertQuantity(ctx, item.IngredientID, item.UnitID, ingredient.UnitID, organizationID, item.Quantity)
				if err != nil {
					return nil, fmt.Errorf("failed to convert quantity for ingredient %s from unit %s to %s: %w", item.IngredientID, item.UnitID, ingredient.UnitID, err)
				}
				quantityToAdd = convertedQuantity
			}

			// Calculate unit cost in ingredient's base unit
			unitCost := 0.0
			if quantityToAdd > 0 {
				unitCost = item.Amount / quantityToAdd
			}

			// Create inventory movement for ingredient purchase
			reason := fmt.Sprintf("Purchase order %s received", po.PONumber)
			referenceType := entities.InventoryMovementReferenceTypePurchaseOrder
			referenceID := &id

			err = p.inventoryMovementService.CreateIngredientMovement(
				ctx,
				item.IngredientID,
				organizationID,
				outletID,
				userID,
				entities.InventoryMovementTypePurchase,
				quantityToAdd,
				unitCost,
				reason,
				&referenceType,
				referenceID,
			)
			if err != nil {
				return nil, fmt.Errorf("failed to create inventory movement for ingredient %s: %w", item.IngredientID, err)
			}
		}
	}

	// Update the purchase order status
	err = p.purchaseOrderRepo.UpdateStatus(ctx, id, status)
	if err != nil {
		return nil, fmt.Errorf("failed to update purchase order status: %w", err)
	}

	// Get the updated purchase order
	updatedPO, err := p.purchaseOrderRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("failed to get updated purchase order: %w", err)
	}

	return mappers.PurchaseOrderEntityToResponse(updatedPO), nil
}
