package eu.reply.lea.services.yard.observer;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.inject.Inject;

import java.util.List;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;

import eu.reply.lea.services.masterdata.model.Container;
import eu.reply.lea.services.masterdata.reference.LocationReference;
import eu.reply.lea.services.yard.application.LoadingUnitMovementApplicationService;
import eu.reply.lea.services.yard.entities.LoadingUnitMovement;
import eu.reply.lea.services.yard.entities.LoadingUnitResource;
import eu.reply.lea.services.yard.entities.LoadingUnitResourceRepository;
import eu.reply.lea.services.yard.entities.Transit;
import eu.reply.lea.services.yard.entities.TransitMovement;
import eu.reply.lea.services.yard.events.loadingUnit.LoadingUnitChangedStatusEvent;
import eu.reply.lea.services.yard.events.movement.MovementStatusChangedEvent;
import eu.reply.lea.services.yard.model.ContainerResource;
import eu.reply.lea.services.yard.model.Movement;
import eu.reply.lea.services.yard.model.TrailerResource;
import eu.reply.lea.services.yard.reference.LoadingUnitReference;
import eu.reply.lea.services.yard.reference.TransitReference;
import eu.reply.lea.services.yard.util.ObjectMapperUtility;
import eu.reply.sce.backend.core.entity.EntityReference;
import eu.reply.sce.backend.core.event.ConfigurableEventProducer;
import eu.reply.sce.backend.core.event.ModuleEvent;
import eu.reply.sce.backend.core.event.Observer;
import eu.reply.sce.backend.core.jee.log.Logged;

@Observer
@Logged
@ApplicationScoped
public class LoadingUnitMovementObserver {

    @Inject
    private ConfigurableEventProducer<LoadingUnitChangedStatusEvent> loadingUnitChangedStatusEventProducer;

    @Inject
    private LoadingUnitMovementApplicationService loadingUnitMovementApplicationService;

    @Inject
    private LoadingUnitResourceRepository loadingUnitResourceRepository;


    public void onMovementStatusChanged(
        @Observes final MovementStatusChangedEvent  event)  {
        if (event.getPayload().getLogisticUnitRef().getEntityName().equals(Transit.RESOURCE_NAME)){
            this.dispatch((TransitMovement) event.getPayload());
        }else if (event.getPayload().getLogisticUnitRef().getEntityName().equals(TrailerResource.RESOURCE_NAME)){
            this.dispatch((LoadingUnitMovement) event.getPayload());
        }else if (event.getPayload().getLogisticUnitRef().getEntityName().equals(ContainerResource.RESOURCE_NAME)){
            this.dispatch((LoadingUnitMovement) event.getPayload());
        } else {
            throw new UnsupportedOperationException(
                "Unknown logistic unit for the movement: " + event.getPayload().getLogisticUnitRef().getEntityName());
        }
    }

    private void dispatch(final TransitMovement movement) {
        if (movement.getStatus().equals(Movement.Status.EXECUTING)){
            List<LoadingUnitResource> loadingUnitResources =
                loadingUnitResourceRepository.findLoadingUnitByAttachedTransit(new TransitReference(movement.getLogisticUnitRef()));
            for (LoadingUnitResource loadingUnitResource : loadingUnitResources) {
                loadingUnitMovementApplicationService.createNewMovement(new LoadingUnitReference(loadingUnitResource), movement.getDestinationLocation());
            }
        } else if (movement.getStatus().equals(Movement.Status.COMPLETED)){
            List<LoadingUnitResource> loadingUnitResources =
                loadingUnitResourceRepository.findLoadingUnitByAttachedTransit(new TransitReference(movement.getLogisticUnitRef()));
            for (LoadingUnitResource loadingUnitResource : loadingUnitResources) {
                loadingUnitMovementApplicationService.completeMovement(new LoadingUnitReference(loadingUnitResource), movement.getDestinationLocation());
            }
        }
    }

    private void dispatch(final LoadingUnitMovement movement) {
        LoadingUnitResource loadingUnitResource = loadingUnitResourceRepository.findByEntityRef(movement.getLoadingUnitRef());
        if (movement.getStatus().equals(Movement.Status.EXECUTING)){
            loadingUnitResource.moving(movement.getSourceLocation(), movement.getDestinationLocation(),
                                       loadingUnitChangedStatusEventProducer);
        } else if (movement.getStatus().equals(Movement.Status.COMPLETED)){
            loadingUnitResource.inPlace(movement.getDestinationLocation(), loadingUnitChangedStatusEventProducer);
        }
    }

    public void updateLoadingUnitRelatedToTransit(
        @Observes @ModuleEvent("LogisticUnitLocationUpdateCommand") final JsonNode event)
        throws JsonProcessingException {
        EntityReference
            logisticUnitRef =
            ObjectMapperUtility.getMapper().treeToValue(event.path("logisticUnitRef"), EntityReference.class);
        LocationReference
            locationReference =
            ObjectMapperUtility.getMapper().treeToValue(event.path("locationReference"), LocationReference.class);
        String
            areaType = ObjectMapperUtility.getMapper().treeToValue(event.path("areaType"), String.class);
        if ( logisticUnitRef==null || !(logisticUnitRef.getEntityName().equals(Container.RESOURCE_NAME) ||
                                        logisticUnitRef.getEntityName().equals(TrailerResource.RESOURCE_NAME))){
            return;
        }
        if (areaType.equals("WAITING_AREA") || areaType.equals("BOOKING_AREA")) {
            loadingUnitMovementApplicationService.createNewMovement(new LoadingUnitReference(logisticUnitRef), locationReference);
            loadingUnitMovementApplicationService.completeMovement(new LoadingUnitReference(logisticUnitRef), locationReference);
        }
        if (areaType.equals("EXECUTION_AREA")) {
            loadingUnitMovementApplicationService.createNewMovement(new LoadingUnitReference(logisticUnitRef), locationReference);
        }
    }

    public void onTaskEnter(
        @Observes @ModuleEvent("completeMovementCommand") final JsonNode event) throws JsonProcessingException {
        EntityReference
            logisticUnitReference = ObjectMapperUtility.getMapper().treeToValue(event.path("logisticUnitReference"), EntityReference.class);
        LocationReference locationOfExecution = ObjectMapperUtility.getMapper().treeToValue(event.path("location"), LocationReference.class);
        if( logisticUnitReference!=null &&
            (logisticUnitReference.getEntityName().equals(Container.RESOURCE_NAME) ||
            logisticUnitReference.getEntityName().equals(TrailerResource.RESOURCE_NAME) )){
            loadingUnitMovementApplicationService.completeMovement(new LoadingUnitReference(logisticUnitReference), locationOfExecution);
        }
    }
}

package eu.reply.lea.services.yard.application;

import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.validation.constraints.NotNull;
import java.util.List;

import eu.reply.lea.services.masterdata.reference.LocationReference;
import eu.reply.lea.services.yard.domainservice.LoadingUnitMovementDomainService;
import eu.reply.lea.services.yard.entities.LoadingUnitMovement;
import eu.reply.lea.services.yard.entities.LoadingUnitResource;
import eu.reply.lea.services.yard.reference.LoadingUnitReference;
import eu.reply.lea.services.yard.repository.LoadingUnitMovementRepository;
import eu.reply.sce.backend.core.jee.log.Logged;


@ApplicationScoped
@Logged
public class LoadingUnitMovementApplicationService {

    @Inject
    private LoadingUnitMovementRepository loadingUnitMovementRepository;
    @Inject
    private LoadingUnitMovementDomainService loadingUnitMovementDomainService;


    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void createNewMovement(@NotNull final LoadingUnitReference loadingUnitRef, final LocationReference destinationLocation) {
        List<LoadingUnitMovement> loadingUnitMovements =
            loadingUnitMovementRepository.findByLoadingUnitRef(loadingUnitRef);
        loadingUnitMovementDomainService.createNewMovement(loadingUnitMovements, loadingUnitRef, destinationLocation);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void createNewMovement(@NotNull final LoadingUnitMovement loadingUnitMovement) {
        loadingUnitMovementDomainService.createNewMovement(loadingUnitMovement);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void completeMovement(@NotNull final LoadingUnitMovement loadingUnitMovement) {
        loadingUnitMovementDomainService.completeMovement(loadingUnitMovement);
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void completeMovement(@NotNull final LoadingUnitReference loadingUnitRef, final LocationReference parkLocation) {
        List<LoadingUnitMovement> loadingUnitMovements =
            loadingUnitMovementRepository.findByLoadingUnitRef(loadingUnitRef);
        loadingUnitMovementDomainService.completeMovement(loadingUnitMovements, loadingUnitRef, parkLocation);
    }

}
package eu.reply.lea.services.yard.domainservice;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.validation.constraints.NotNull;
import java.util.List;

import lombok.NonNull;

import eu.reply.lea.services.masterdata.reference.LocationReference;
import eu.reply.lea.services.yard.entities.LoadingUnitMovement;
import eu.reply.lea.services.yard.entities.LoadingUnitResource;
import eu.reply.lea.services.yard.entities.Movement;
import eu.reply.lea.services.yard.events.movement.MovementStatusChangedEvent;
import eu.reply.lea.services.yard.reference.LoadingUnitReference;
import eu.reply.sce.backend.core.entity.EntityReference;
import eu.reply.sce.backend.core.event.ConfigurableEventProducer;
import eu.reply.sce.backend.core.jee.log.Logged;

/**
 * Service class for managing loading unit movements.
 * This service provides methods to start and complete movements for loading units,
 * ensuring consistency and adherence to business rules.
 */
@ApplicationScoped
@Logged
public class LoadingUnitMovementDomainService extends MovementDomainService {

    @Inject
    private ConfigurableEventProducer<MovementStatusChangedEvent> statusChangedEventProducer;


    /**
     * Starts a new movement or continues an existing one for a loading unit to a specified destination location.
     *
     * @param loadingUnitMovements List of existing loading unit movements.
     * @param loadingUnitRef  The loading unit resource ref.
     * @param destinationLocation  The destination location for the movement.
     */
    public void createNewMovement(final List<LoadingUnitMovement> loadingUnitMovements, final LoadingUnitReference loadingUnitRef,
                                  final LocationReference destinationLocation) {
        super.createNewMovement(loadingUnitMovements, loadingUnitRef.toEntityReference(),
                                destinationLocation);
    }

    public void createNewMovement(@NotNull final LoadingUnitMovement loadingUnitMovement) {
        loadingUnitMovement.executing(statusChangedEventProducer);
    }

    public void completeMovement(@NotNull final LoadingUnitMovement loadingUnitMovement) {
        loadingUnitMovement.completed(statusChangedEventProducer);
    }

    /**
     * Completes the movement for a loading unit and parks it at a specified location if it is moving.
     *
     * @param loadingUnitMovements List of existing loading unit movements.
     * @param loadingUnitReference  The loading unit resource reference.
     * @param parkLocation         The park location.
     */
    public void completeMovement(final List<LoadingUnitMovement> loadingUnitMovements,
                                 final LoadingUnitReference loadingUnitReference, final LocationReference parkLocation) {
        super.completeMovement(loadingUnitMovements, loadingUnitReference.toEntityReference(),
                                   parkLocation);

    }

    @Override
    protected Movement createNewInstance(final @NonNull EntityReference logisticUnitRef, final LocationReference sourceLocation,
                                         final LocationReference destinationLocation) {
        return new LoadingUnitMovement(logisticUnitRef, sourceLocation, destinationLocation);
    }


}
package eu.reply.lea.services.yard.domainservice;

import java.util.Comparator;
import java.util.List;
import org.slf4j.Logger;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.persistence.EntityManager;

import lombok.NonNull;

import eu.reply.lea.services.masterdata.reference.LocationReference;
import eu.reply.lea.services.yard.entities.Movement;
import eu.reply.lea.services.yard.events.movement.MovementStatusChangedEvent;
import eu.reply.sce.backend.core.entity.EntityReference;
import eu.reply.sce.backend.core.event.ConfigurableEventProducer;
import eu.reply.sce.backend.core.jee.configuration.ModuleConfiguration;
import eu.reply.sce.backend.core.jee.log.Logged;

@Logged
public abstract class MovementDomainService { //TODO it's probably more appropriate to create an entity "TransitMovementSequence" and "LoadingUnitMovementSequence"

    @Inject
    @ModuleConfiguration.Module(ModuleConfiguration.Module.CURRENT)
    private EntityManager em;

    @Inject
    private ConfigurableEventProducer<MovementStatusChangedEvent> statusChangedEventProducer;

    @Inject
    private Logger log;


    <K extends Movement> Movement createNewMovement(final List<K> existingMovements, final EntityReference logisticUnit, final LocationReference destinationLocation) {
        completeExecutingMovementsToOtherLocations(existingMovements, destinationLocation);
        cancelToDoMovementsToOtherLocations(existingMovements, destinationLocation);
        K lastCompletedMovement = findLastCompletedMovement(existingMovements);

        if( lastCompletedMovement != null && lastCompletedMovement.getDestinationLocation().equals(destinationLocation)) {
            log.info("The last completed movement has the same destination, therefore the movement will not be created, completed movement: {}", lastCompletedMovement.getCode());
            return lastCompletedMovement;
        }

        List<K> executingMovements = findByStatusAndDestination(existingMovements, Movement.Status.EXECUTING, destinationLocation);
        List<K> toDoMovements = findByStatusAndDestination(existingMovements, Movement.Status.TO_DO, destinationLocation);

        validateAtMostOneExecutionOrTodoMovementIsPresent(executingMovements, toDoMovements, destinationLocation, logisticUnit);
        if (!executingMovements.isEmpty()) {
            return executingMovements.get(0);
        }else if (!toDoMovements.isEmpty()) {
            toDoMovements.get(0).executing(statusChangedEventProducer);
            return toDoMovements.get(0);
        }else {
            LocationReference lastLocation = lastCompletedMovement != null ? lastCompletedMovement.getDestinationLocation() : null;
            Movement newMovement = createAndPersistNewMovement(lastLocation, logisticUnit, destinationLocation);
            newMovement.executing(statusChangedEventProducer);
            return newMovement;
        }
    }


    public <K extends Movement> K completeMovement(final List<K> existingMovements, final EntityReference logisticUnit, final LocationReference destinationLocation) {
        completeExecutingMovementsToOtherLocations(existingMovements,destinationLocation);
        cancelToDoMovementsToOtherLocations(existingMovements, destinationLocation);

        K lastCompletedMovement = findLastCompletedMovement(existingMovements);
        if( lastCompletedMovement != null && lastCompletedMovement.getDestinationLocation().equals(destinationLocation)) {
            log.info("The last completed movement has the same destination, completed movement: {}", lastCompletedMovement.getCode());
            return lastCompletedMovement;
        }

        List<K> executingMovements = findByStatusAndDestination(existingMovements, Movement.Status.EXECUTING, destinationLocation);

        if (executingMovements.isEmpty()) {
            throw new IllegalStateException(String.format("No movements in execution to the destination location %s for logisticUnit %s", destinationLocation, logisticUnit ));
        }
        if(executingMovements.size() > 1) {
            throw new IllegalStateException(String.format("Too many movements in execution to the destination location %s for logisticUnit %s", destinationLocation, logisticUnit ));
        }
        executingMovements.get(0).completed(statusChangedEventProducer);
        return executingMovements.get(0);
    }

    protected abstract <K extends Movement> K createNewInstance(@NonNull final EntityReference logisticUnitRef, final LocationReference sourceLocation,
                                              final LocationReference destinationLocation);


    private <K extends Movement>  void validateAtMostOneExecutionOrTodoMovementIsPresent(List<K> executingMovements, List<K> toDoMovements, LocationReference destinationLocation, EntityReference logisticUnit) {
        int executingSize = executingMovements.size();
        int toDoSize = toDoMovements.size();

        if (executingSize > 1) {
            throw new IllegalStateException(String.format("Too many movements in execution to the destination location %s for logisticUnit %s", destinationLocation, logisticUnit));
        }

        if (executingSize == 1 && toDoSize > 0) {
            throw new IllegalStateException(String.format("There are movements in both todo and executing for the same destination for logisticUnit %s and destination %s", logisticUnit, destinationLocation));
        }

        if (toDoSize > 1) {
            throw new IllegalStateException(String.format("Too many movements in TODO to the destination location %s for logisticUnit %s", destinationLocation, logisticUnit));
        }
    }

    public void completeExecutingMovementsToOtherLocations(List<? extends Movement> movements, LocationReference destinationLocation) {
        movements.stream()
            .filter(mov -> Movement.Status.isExecuting(mov.getStatus()) && !mov.getDestinationLocation().equals(destinationLocation))
            .forEach(mov -> mov.completed(statusChangedEventProducer));
    }

    public void cancelToDoMovementsToOtherLocations(List<? extends Movement> movements, LocationReference destinationLocation) {
        movements.stream()
            .filter(mov -> Movement.Status.isTodo(mov.getStatus()) && !mov.getDestinationLocation().equals(destinationLocation))
            .forEach(mov -> mov.cancel(statusChangedEventProducer));
    }

    private <K extends Movement> List<K> findByStatusAndDestination(List<K> movements, Movement.Status status, LocationReference destinationLocation) {
        return movements.stream()
            .filter(mov -> mov.getStatus().equals(status) && mov.getDestinationLocation().equals(destinationLocation))
            .collect(Collectors.toList());
    }

    private <K extends Movement> K createAndPersistNewMovement(LocationReference lastLocation, EntityReference logisticUnit, LocationReference destinationLocation) {
        K newMovement = createNewInstance(logisticUnit, lastLocation, destinationLocation);
        em.persist(newMovement);
        return newMovement;
    }

    public <K extends Movement> K findLastCompletedMovement(List<K> movements){
        return movements.stream()
            .filter(mov -> mov.getStatus().equals(Movement.Status.COMPLETED))
            .max(Comparator.comparing(Movement::getMovementEndDate)).orElse(null);
    }

}

Something to solve here, considering DDD principles, to solve fast, i don't want to spend too much time