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 IngredientUnitConverterRepository interface {
	Create(ctx context.Context, converter *entities.IngredientUnitConverter) error
	GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error)
	GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error)
	Update(ctx context.Context, converter *entities.IngredientUnitConverter) error
	Delete(ctx context.Context, id, organizationID uuid.UUID) error
	List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*entities.IngredientUnitConverter, int, error)
	GetByIngredientAndUnits(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error)
	GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error)
	GetActiveConverters(ctx context.Context, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error)
	ConvertQuantity(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID, quantity float64) (float64, error)
}

type IngredientUnitConverterProcessor interface {
	CreateIngredientUnitConverter(ctx context.Context, organizationID, userID uuid.UUID, req *models.CreateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error)
	UpdateIngredientUnitConverter(ctx context.Context, id, organizationID, userID uuid.UUID, req *models.UpdateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error)
	DeleteIngredientUnitConverter(ctx context.Context, id, organizationID uuid.UUID) error
	GetIngredientUnitConverterByID(ctx context.Context, id, organizationID uuid.UUID) (*models.IngredientUnitConverterResponse, error)
	ListIngredientUnitConverters(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.IngredientUnitConverterResponse, int, error)
	GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.IngredientUnitConverterResponse, error)
	ConvertUnit(ctx context.Context, organizationID uuid.UUID, req *models.ConvertUnitRequest) (*models.ConvertUnitResponse, error)
}

type IngredientUnitConverterProcessorImpl struct {
	converterRepo  IngredientUnitConverterRepository
	ingredientRepo IngredientRepository
	unitRepo       UnitRepository
}

func NewIngredientUnitConverterProcessorImpl(
	converterRepo IngredientUnitConverterRepository,
	ingredientRepo IngredientRepository,
	unitRepo UnitRepository,
) *IngredientUnitConverterProcessorImpl {
	return &IngredientUnitConverterProcessorImpl{
		converterRepo:  converterRepo,
		ingredientRepo: ingredientRepo,
		unitRepo:       unitRepo,
	}
}

func (p *IngredientUnitConverterProcessorImpl) CreateIngredientUnitConverter(ctx context.Context, organizationID, userID uuid.UUID, req *models.CreateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error) {
	// Validate ingredient exists
	_, err := p.ingredientRepo.GetByID(ctx, req.IngredientID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("ingredient not found: %w", err)
	}

	// Validate units exist
	_, err = p.unitRepo.GetByID(ctx, req.FromUnitID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("from unit not found: %w", err)
	}

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

	// Check if converter already exists
	existingConverter, err := p.converterRepo.GetByIngredientAndUnits(ctx, req.IngredientID, req.FromUnitID, req.ToUnitID, organizationID)
	if err == nil && existingConverter != nil {
		return nil, fmt.Errorf("converter already exists for this ingredient and unit combination")
	}

	// Set default values
	isActive := true
	if req.IsActive != nil {
		isActive = *req.IsActive
	}

	// Create entity
	converter := &entities.IngredientUnitConverter{
		OrganizationID:   organizationID,
		IngredientID:     req.IngredientID,
		FromUnitID:       req.FromUnitID,
		ToUnitID:         req.ToUnitID,
		ConversionFactor: req.ConversionFactor,
		IsActive:         isActive,
		CreatedBy:        userID,
		UpdatedBy:        userID,
	}

	err = p.converterRepo.Create(ctx, converter)
	if err != nil {
		return nil, fmt.Errorf("failed to create ingredient unit converter: %w", err)
	}

	// Get the created converter with relationships
	createdConverter, err := p.converterRepo.GetByID(ctx, converter.ID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get created converter: %w", err)
	}

	return mappers.IngredientUnitConverterEntityToResponse(createdConverter), nil
}

func (p *IngredientUnitConverterProcessorImpl) UpdateIngredientUnitConverter(ctx context.Context, id, organizationID, userID uuid.UUID, req *models.UpdateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error) {
	// Get existing converter
	converter, err := p.converterRepo.GetByID(ctx, id, organizationID)
	if err != nil {
		return nil, fmt.Errorf("ingredient unit converter not found: %w", err)
	}

	// Update fields if provided
	if req.FromUnitID != nil {
		// Validate new unit exists
		_, err = p.unitRepo.GetByID(ctx, *req.FromUnitID, organizationID)
		if err != nil {
			return nil, fmt.Errorf("from unit not found: %w", err)
		}
		converter.FromUnitID = *req.FromUnitID
	}

	if req.ToUnitID != nil {
		// Validate new unit exists
		_, err = p.unitRepo.GetByID(ctx, *req.ToUnitID, organizationID)
		if err != nil {
			return nil, fmt.Errorf("to unit not found: %w", err)
		}
		converter.ToUnitID = *req.ToUnitID
	}

	if req.ConversionFactor != nil {
		converter.ConversionFactor = *req.ConversionFactor
	}

	if req.IsActive != nil {
		converter.IsActive = *req.IsActive
	}

	converter.UpdatedBy = userID

	err = p.converterRepo.Update(ctx, converter)
	if err != nil {
		return nil, fmt.Errorf("failed to update ingredient unit converter: %w", err)
	}

	// Get the updated converter with relationships
	updatedConverter, err := p.converterRepo.GetByID(ctx, converter.ID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get updated converter: %w", err)
	}

	return mappers.IngredientUnitConverterEntityToResponse(updatedConverter), nil
}

func (p *IngredientUnitConverterProcessorImpl) DeleteIngredientUnitConverter(ctx context.Context, id, organizationID uuid.UUID) error {
	// Check if converter exists
	_, err := p.converterRepo.GetByID(ctx, id, organizationID)
	if err != nil {
		return fmt.Errorf("ingredient unit converter not found: %w", err)
	}

	err = p.converterRepo.Delete(ctx, id, organizationID)
	if err != nil {
		return fmt.Errorf("failed to delete ingredient unit converter: %w", err)
	}

	return nil
}

func (p *IngredientUnitConverterProcessorImpl) GetIngredientUnitConverterByID(ctx context.Context, id, organizationID uuid.UUID) (*models.IngredientUnitConverterResponse, error) {
	converter, err := p.converterRepo.GetByID(ctx, id, organizationID)
	if err != nil {
		return nil, fmt.Errorf("ingredient unit converter not found: %w", err)
	}

	return mappers.IngredientUnitConverterEntityToResponse(converter), nil
}

func (p *IngredientUnitConverterProcessorImpl) ListIngredientUnitConverters(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.IngredientUnitConverterResponse, int, error) {
	converters, total, err := p.converterRepo.List(ctx, organizationID, filters, page, limit)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to list ingredient unit converters: %w", err)
	}

	responses := make([]*models.IngredientUnitConverterResponse, len(converters))
	for i, converter := range converters {
		responses[i] = mappers.IngredientUnitConverterEntityToResponse(converter)
	}

	return responses, total, nil
}

func (p *IngredientUnitConverterProcessorImpl) GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.IngredientUnitConverterResponse, error) {
	converters, err := p.converterRepo.GetConvertersForIngredient(ctx, ingredientID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("failed to get converters for ingredient: %w", err)
	}

	responses := make([]*models.IngredientUnitConverterResponse, len(converters))
	for i, converter := range converters {
		responses[i] = mappers.IngredientUnitConverterEntityToResponse(converter)
	}

	return responses, nil
}

func (p *IngredientUnitConverterProcessorImpl) ConvertUnit(ctx context.Context, organizationID uuid.UUID, req *models.ConvertUnitRequest) (*models.ConvertUnitResponse, error) {
	// Get ingredient and units for response
	ingredient, err := p.ingredientRepo.GetByID(ctx, req.IngredientID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("ingredient not found: %w", err)
	}

	fromUnit, err := p.unitRepo.GetByID(ctx, req.FromUnitID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("from unit not found: %w", err)
	}

	toUnit, err := p.unitRepo.GetByID(ctx, req.ToUnitID, organizationID)
	if err != nil {
		return nil, fmt.Errorf("to unit not found: %w", err)
	}

	// Convert quantity
	convertedQuantity, err := p.converterRepo.ConvertQuantity(ctx, req.IngredientID, req.FromUnitID, req.ToUnitID, organizationID, req.Quantity)
	if err != nil {
		return nil, fmt.Errorf("failed to convert quantity: %w", err)
	}

	// Get conversion factor for response
	converter, err := p.converterRepo.GetByIngredientAndUnits(ctx, req.IngredientID, req.FromUnitID, req.ToUnitID, organizationID)
	var conversionFactor float64
	if err == nil {
		conversionFactor = converter.ConversionFactor
	} else {
		// Try reverse converter
		reverseConverter, err := p.converterRepo.GetByIngredientAndUnits(ctx, req.IngredientID, req.ToUnitID, req.FromUnitID, organizationID)
		if err == nil {
			conversionFactor = 1.0 / reverseConverter.ConversionFactor
		}
	}

	response := &models.ConvertUnitResponse{
		FromQuantity:     req.Quantity,
		FromUnit:         mappers.MapUnitEntityToResponse(fromUnit),
		ToQuantity:       convertedQuantity,
		ToUnit:           mappers.MapUnitEntityToResponse(toUnit),
		ConversionFactor: conversionFactor,
		Ingredient:       mappers.MapIngredientEntityToResponse(ingredient),
	}

	return response, nil
}
