package processor

import (
	"context"
	"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"
)

type InventoryMovementProcessor interface {
	CreateMovement(ctx context.Context, req *models.CreateInventoryMovementRequest) (*models.InventoryMovementResponse, error)
	GetMovementByID(ctx context.Context, id uuid.UUID) (*models.InventoryMovementResponse, error)
	ListMovements(ctx context.Context, req *models.ListInventoryMovementsRequest) (*models.ListInventoryMovementsResponse, error)
	GetMovementsByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) (*models.ListInventoryMovementsResponse, error)
	GetMovementsByOrderID(ctx context.Context, orderID uuid.UUID) ([]models.InventoryMovementResponse, error)
	GetMovementsByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]models.InventoryMovementResponse, error)
}

type InventoryMovementRepository interface {
	Create(ctx context.Context, movement *entities.InventoryMovement) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.InventoryMovement, error)
	GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.InventoryMovement, error)
	List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.InventoryMovement, int64, error)
	GetByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) ([]*entities.InventoryMovement, int64, error)
	GetByOrderID(ctx context.Context, orderID uuid.UUID) ([]*entities.InventoryMovement, error)
	GetByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]*entities.InventoryMovement, error)
	Count(ctx context.Context, filters map[string]interface{}) (int64, error)
}

type InventoryMovementProcessorImpl struct {
	movementRepo  InventoryMovementRepository
	inventoryRepo repository.InventoryRepository
}

func NewInventoryMovementProcessorImpl(
	movementRepo InventoryMovementRepository,
	inventoryRepo repository.InventoryRepository,
) *InventoryMovementProcessorImpl {
	return &InventoryMovementProcessorImpl{
		movementRepo:  movementRepo,
		inventoryRepo: inventoryRepo,
	}
}

func (p *InventoryMovementProcessorImpl) CreateInventoryMovement(ctx context.Context, req *models.CreateInventoryMovementRequest) (*models.InventoryMovementResponse, error) {
	currentInventory, err := p.inventoryRepo.GetByProductAndOutlet(ctx, req.ItemID, req.OutletID)
	if err != nil {
		return nil, fmt.Errorf("failed to get current inventory: %w", err)
	}

	previousQuantity := currentInventory.Quantity
	newQuantity := previousQuantity + req.Quantity

	movement := &entities.InventoryMovement{
		OrganizationID:   req.OrganizationID,
		OutletID:         req.OutletID,
		ItemID:           req.ItemID,
		ItemType:         req.ItemType,
		MovementType:     entities.InventoryMovementType(req.MovementType),
		Quantity:         float64(req.Quantity),
		PreviousQuantity: float64(previousQuantity),
		NewQuantity:      float64(newQuantity),
		UnitCost:         req.UnitCost,
		TotalCost:        float64(req.Quantity) * req.UnitCost,
		ReferenceType:    (*entities.InventoryMovementReferenceType)(req.ReferenceType),
		ReferenceID:      req.ReferenceID,
		OrderID:          req.OrderID,
		PaymentID:        req.PaymentID,
		UserID:           req.UserID,
		Reason:           req.Reason,
		Notes:            req.Notes,
		Metadata:         entities.Metadata(req.Metadata),
	}

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

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

	response := mappers.InventoryMovementEntityToResponse(movementWithRelations)
	return response, nil
}

func (p *InventoryMovementProcessorImpl) GetInventoryMovementByID(ctx context.Context, id uuid.UUID) (*models.InventoryMovementResponse, error) {
	movement, err := p.movementRepo.GetWithRelations(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("movement not found: %w", err)
	}

	response := mappers.InventoryMovementEntityToResponse(movement)
	return response, nil
}

func (p *InventoryMovementProcessorImpl) ListInventoryMovements(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, page, limit int, search string) (*models.PaginatedResponse[models.InventoryMovementResponse], error) {
	// Set default values
	if page < 1 {
		page = 1
	}
	if limit < 1 {
		limit = 10
	}
	if limit > 100 {
		limit = 100
	}

	filters := make(map[string]interface{})
	filters["organization_id"] = organizationID
	if outletID != nil {
		filters["outlet_id"] = *outletID
	}
	if search != "" {
		filters["search"] = search
	}

	offset := (page - 1) * limit
	movements, total, err := p.movementRepo.List(ctx, filters, limit, offset)
	if err != nil {
		return nil, fmt.Errorf("failed to list movements: %w", err)
	}

	// Convert to responses
	movementResponses := make([]models.InventoryMovementResponse, len(movements))
	for i, movement := range movements {
		response := mappers.InventoryMovementEntityToResponse(movement)
		if response != nil {
			movementResponses[i] = *response
		}
	}

	// Create paginated response
	paginatedResponse := &models.PaginatedResponse[models.InventoryMovementResponse]{
		Data: movementResponses,
		Pagination: models.Pagination{
			Page:       page,
			Limit:      limit,
			Total:      total,
			TotalPages: int((total + int64(limit) - 1) / int64(limit)),
		},
	}

	return paginatedResponse, nil
}

func (p *InventoryMovementProcessorImpl) GetMovementsByProductAndOutlet(ctx context.Context, productID, outletID uuid.UUID, limit, offset int) (*models.ListInventoryMovementsResponse, error) {
	movements, total, err := p.movementRepo.GetByProductAndOutlet(ctx, productID, outletID, limit, offset)
	if err != nil {
		return nil, fmt.Errorf("failed to get movements by product and outlet: %w", err)
	}

	movementResponses := make([]models.InventoryMovementResponse, len(movements))
	for i, movement := range movements {
		response := mappers.InventoryMovementEntityToResponse(movement)
		if response != nil {
			movementResponses[i] = *response
		}
	}

	totalPages := int(total) / limit
	if int(total)%limit > 0 {
		totalPages++
	}

	return &models.ListInventoryMovementsResponse{
		Movements:  movementResponses,
		TotalCount: int(total),
		Page:       1,
		Limit:      limit,
		TotalPages: totalPages,
	}, nil
}

func (p *InventoryMovementProcessorImpl) GetMovementsByOrderID(ctx context.Context, orderID uuid.UUID) ([]models.InventoryMovementResponse, error) {
	movements, err := p.movementRepo.GetByOrderID(ctx, orderID)
	if err != nil {
		return nil, fmt.Errorf("failed to get movements by order ID: %w", err)
	}

	responses := mappers.InventoryMovementEntitiesToResponses(movements)
	return responses, nil
}

func (p *InventoryMovementProcessorImpl) GetMovementsByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]models.InventoryMovementResponse, error) {
	movements, err := p.movementRepo.GetByPaymentID(ctx, paymentID)
	if err != nil {
		return nil, fmt.Errorf("failed to get movements by payment ID: %w", err)
	}

	responses := mappers.InventoryMovementEntitiesToResponses(movements)
	return responses, nil
}
