package de.effigenix.infrastructure.inventory.persistence.mapper;

import de.effigenix.domain.inventory.*;
import de.effigenix.domain.masterdata.ArticleId;
import de.effigenix.shared.common.Quantity;
import de.effigenix.shared.common.UnitOfMeasure;
import de.effigenix.infrastructure.inventory.persistence.entity.ReservationEntity;
import de.effigenix.infrastructure.inventory.persistence.entity.StockBatchAllocationEntity;
import de.effigenix.infrastructure.inventory.persistence.entity.StockBatchEntity;
import de.effigenix.infrastructure.inventory.persistence.entity.StockEntity;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.stream.Collectors;

@Component
public class StockMapper {

    public StockEntity toEntity(Stock stock) {
        var entity = new StockEntity();
        entity.setId(stock.id().value());
        entity.setArticleId(stock.articleId().value());
        entity.setStorageLocationId(stock.storageLocationId().value());

        if (stock.minimumLevel() != null) {
            entity.setMinimumLevelAmount(stock.minimumLevel().quantity().amount());
            entity.setMinimumLevelUnit(stock.minimumLevel().quantity().uom().name());
        }

        if (stock.minimumShelfLife() != null) {
            entity.setMinimumShelfLifeDays(stock.minimumShelfLife().days());
        }

        List<StockBatchEntity> batchEntities = stock.batches().stream()
                .map(b -> toBatchEntity(b, entity))
                .collect(Collectors.toList());
        entity.setBatches(batchEntities);

        List<ReservationEntity> reservationEntities = stock.reservations().stream()
                .map(r -> toReservationEntity(r, entity))
                .collect(Collectors.toList());
        entity.setReservations(reservationEntities);

        return entity;
    }

    public Stock toDomain(StockEntity entity) {
        MinimumLevel minimumLevel = null;
        if (entity.getMinimumLevelAmount() != null && entity.getMinimumLevelUnit() != null) {
            var quantity = Quantity.reconstitute(
                    entity.getMinimumLevelAmount(),
                    UnitOfMeasure.valueOf(entity.getMinimumLevelUnit())
            );
            minimumLevel = new MinimumLevel(quantity);
        }

        MinimumShelfLife minimumShelfLife = null;
        if (entity.getMinimumShelfLifeDays() != null) {
            minimumShelfLife = new MinimumShelfLife(entity.getMinimumShelfLifeDays());
        }

        List<StockBatch> batches = entity.getBatches().stream()
                .map(this::toDomainBatch)
                .collect(Collectors.toList());

        List<Reservation> reservations = entity.getReservations().stream()
                .map(this::toDomainReservation)
                .collect(Collectors.toList());

        return Stock.reconstitute(
                StockId.of(entity.getId()),
                ArticleId.of(entity.getArticleId()),
                StorageLocationId.of(entity.getStorageLocationId()),
                minimumLevel,
                minimumShelfLife,
                batches,
                reservations
        );
    }

    private StockBatchEntity toBatchEntity(StockBatch batch, StockEntity stockEntity) {
        return new StockBatchEntity(
                batch.id().value(),
                stockEntity,
                batch.batchReference().batchId(),
                batch.batchReference().batchType().name(),
                batch.quantity().amount(),
                batch.quantity().uom().name(),
                batch.expiryDate(),
                batch.status().name(),
                batch.receivedAt()
        );
    }

    private StockBatch toDomainBatch(StockBatchEntity entity) {
        return StockBatch.reconstitute(
                StockBatchId.of(entity.getId()),
                new BatchReference(entity.getBatchId(), BatchType.valueOf(entity.getBatchType())),
                Quantity.reconstitute(
                        entity.getQuantityAmount(),
                        UnitOfMeasure.valueOf(entity.getQuantityUnit())
                ),
                entity.getExpiryDate(),
                StockBatchStatus.valueOf(entity.getStatus()),
                entity.getReceivedAt()
        );
    }

    private ReservationEntity toReservationEntity(Reservation reservation, StockEntity stockEntity) {
        var entity = new ReservationEntity(
                reservation.id().value(),
                stockEntity,
                reservation.referenceType().name(),
                reservation.referenceId(),
                reservation.quantity().amount(),
                reservation.quantity().uom().name(),
                reservation.priority().name(),
                reservation.reservedAt()
        );

        List<StockBatchAllocationEntity> allocationEntities = reservation.allocations().stream()
                .map(a -> toAllocationEntity(a, entity))
                .collect(Collectors.toList());
        entity.setAllocations(allocationEntities);

        return entity;
    }

    private StockBatchAllocationEntity toAllocationEntity(StockBatchAllocation allocation, ReservationEntity reservationEntity) {
        return new StockBatchAllocationEntity(
                allocation.id().value(),
                reservationEntity,
                allocation.stockBatchId().value(),
                allocation.allocatedQuantity().amount(),
                allocation.allocatedQuantity().uom().name()
        );
    }

    private Reservation toDomainReservation(ReservationEntity entity) {
        List<StockBatchAllocation> allocations = entity.getAllocations().stream()
                .map(this::toDomainAllocation)
                .collect(Collectors.toList());

        return new Reservation(
                ReservationId.of(entity.getId()),
                ReferenceType.valueOf(entity.getReferenceType()),
                entity.getReferenceId(),
                Quantity.reconstitute(
                        entity.getQuantityAmount(),
                        UnitOfMeasure.valueOf(entity.getQuantityUnit())
                ),
                ReservationPriority.valueOf(entity.getPriority()),
                entity.getReservedAt(),
                allocations
        );
    }

    private StockBatchAllocation toDomainAllocation(StockBatchAllocationEntity entity) {
        return new StockBatchAllocation(
                AllocationId.of(entity.getId()),
                StockBatchId.of(entity.getStockBatchId()),
                Quantity.reconstitute(
                        entity.getAllocatedQuantityAmount(),
                        UnitOfMeasure.valueOf(entity.getAllocatedQuantityUnit())
                )
        );
    }
}
