package com.intrasoft.ermis.transit.cargoofficeofdestination.core.service;

import com.intrasoft.ermis.cargov1.messages.MessageTypes;
import com.intrasoft.ermis.cargov1.messages.TB015CType;
import com.intrasoft.ermis.common.logging.ErmisLogger;
import com.intrasoft.ermis.common.xml.parsing.XmlConverter;
import com.intrasoft.ermis.transit.cargoofficeofdestination.core.process.CargoOoDestinationArrivalNotificationFacade;
import com.intrasoft.ermis.transit.cargoofficeofdestination.core.process.CargoOoDestinationInvalidationFacade;
import com.intrasoft.ermis.transit.cargoofficeofdestination.core.process.CargoOoDestinationMainFacade;
import com.intrasoft.ermis.transit.cargoofficeofdestination.domain.Declaration;
import com.intrasoft.ermis.transit.cargoofficeofdestination.domain.Message;
import com.intrasoft.ermis.transit.cargoofficeofdestination.domain.Movement;
import com.intrasoft.ermis.transit.common.exceptions.BaseException;
import com.intrasoft.ermis.transit.common.exceptions.NotFoundException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import javax.xml.bind.JAXBException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class ProcessMessageServiceImpl implements ProcessMessageService {

    private final ErmisLogger logger;

    private final DossierDataService dossierDataService;

    private final CargoOoDestinationMainFacade mainFacade;

    private final CargoOoDestinationArrivalNotificationFacade arrivalNotificationFacade;

    private final CargoOoDestinationInvalidationFacade invalidationFacade;

    @Override
    public void process(String messageId, String messageType, String movementId) {

        try {

            MessageTypes cargoMessageType = MessageTypes.fromValue(messageType);

            switch (cargoMessageType) {
                case TB_007_C:
                    handleTB007C(movementId, messageId);
                    break;
                case TB_014_C:
                    handleTB014C(movementId, messageId);
                    break;
                case TB_015_C:
                    handleTB015C(movementId, messageId);
                    break;
                default:
                    logger.warn("Failed to process Message with type: {}, ignoring.", cargoMessageType.value());
                    break;
            }
        } catch (IllegalArgumentException exception) {
            logger.error(String.format("process(): MESSAGE PROCESSING EXCEPTION -> messageType: [%s], messageId: [%s], movementId: [%s]",
                    messageType, messageId, movementId), exception);
        }
    }

    private void handleTB007C(String movementId, String messageId) {

        arrivalNotificationFacade.initProcessInstance(movementId, messageId);
    }

    private void handleTB014C(String movementId, String messageId) {

        invalidationFacade.initProcessInstance(movementId, messageId);
    }

    private void handleTB015C(String movementId, String messageId) {

        Movement movement = getMovement(movementId);
        Message message = getMessage(messageId);

        if (movement != null && message != null) {
            // Create Declaration and update Movement:
            createDeclaration(message, movementId);

            updateMovementWithDeclarationDataFromMessage(movement, message);

            mainFacade.initProcessInstance(movementId, messageId);
        }
    }

    private Movement getMovement(String movementId) {

        try {
            return dossierDataService.getMovementById(movementId);
        } catch (NotFoundException e) {
            logger.info("No Movement with ID: {} was found.", movementId);
            return null;
        }
    }

    private Message getMessage(String messageId) {

        try {
            return dossierDataService.getMessageById(messageId);
        } catch (NotFoundException e) {
            logger.info("No Message with ID: {} was found.", messageId);
            return null;
        }
    }

    private void createDeclaration(Message message, String movementId) {

        logger.info("Create Declaration for messageId: {} and movementId: {}", message.getId(), movementId);

        Declaration declaration = Declaration.builder()
                                             .movementId(movementId)
                                             .payloadType(message.getMessageType())
                                             .payload(message.getPayload())
                                             .build();

        dossierDataService.createDeclaration(declaration);
    }

    private void updateMovementWithDeclarationDataFromMessage(Movement movement, Message message) {

        try {

            TB015CType tb015cTypeMessage = XmlConverter.buildMessageFromXML(message.getPayload(), TB015CType.class);

            movement.setDeclarationType(tb015cTypeMessage.getTransitOperation().getDeclarationType());
            movement.setSubmitterIdentification(tb015cTypeMessage.getHolderOfTheTransitProcedure().getIdentificationNumber());
            movement.setOfficeOfDeparture(tb015cTypeMessage.getCustomsOfficeOfDeparture().getReferenceNumber());
            movement.setOfficeOfDestination(tb015cTypeMessage.getCustomsOfficeOfDestinationDeclared().getReferenceNumber());
            movement.setCountryOfDestination(tb015cTypeMessage.getConsignment().getCountryOfDestination());
            movement.setAcceptanceDateTime(LocalDateTime.now(ZoneOffset.UTC));
            dossierDataService.updateMovement(movement);

        } catch (JAXBException e) {

            throw new BaseException("updateMovementWithDeclarationData(): ERROR during Message XML conversion.", e);
        }
    }
}

package com.intrasoft.ermis.transit.cargoofficeofdestination.test.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.intrasoft.ermis.cargov1.messages.MessageTypes;
import com.intrasoft.ermis.cargov1.messages.TB015CType;
import com.intrasoft.ermis.common.logging.ErmisLogger;
import com.intrasoft.ermis.common.xml.enums.NamespacesEnum;
import com.intrasoft.ermis.common.xml.parsing.XmlConverter;
import com.intrasoft.ermis.transit.cargoofficeofdestination.core.process.CargoOoDestinationArrivalNotificationFacade;
import com.intrasoft.ermis.transit.cargoofficeofdestination.core.process.CargoOoDestinationInvalidationFacade;
import com.intrasoft.ermis.transit.cargoofficeofdestination.core.process.CargoOoDestinationMainFacade;
import com.intrasoft.ermis.transit.cargoofficeofdestination.core.service.DossierDataService;
import com.intrasoft.ermis.transit.cargoofficeofdestination.core.service.ProcessMessageServiceImpl;
import com.intrasoft.ermis.transit.cargoofficeofdestination.domain.Declaration;
import com.intrasoft.ermis.transit.cargoofficeofdestination.domain.Message;
import com.intrasoft.ermis.transit.cargoofficeofdestination.domain.Movement;
import com.intrasoft.ermis.transit.cargoofficeofdestination.test.util.TestDataUtilities;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class ProcessMessageServiceUT {

    private static final String MOVEMENT_ID_PREFIX = "MOVEMENT_ID_";

    private static final String MESSAGE_ID_PREFIX = "MESSAGE_ID_";

    private static final String TEST_DEPARTURE_OFFICE = "DK000000";

    private static final String TEST_DESTINATION_OFFICE = "DK111111";

    @Mock
    private ErmisLogger logger;

    @Mock
    private DossierDataService dossierDataService;

    @Mock
    private CargoOoDestinationMainFacade mainFacade;

    @Mock
    private CargoOoDestinationArrivalNotificationFacade arrivalNotificationFacade;

    @Mock
    private CargoOoDestinationInvalidationFacade invalidationFacade;

    @InjectMocks
    private ProcessMessageServiceImpl processMessageService;

    @Captor
    private ArgumentCaptor<Declaration> declarationArgumentCaptor;

    @Captor
    private ArgumentCaptor<Movement> movementArgumentCaptor;

    @ParameterizedTest
    @DisplayName("Test - Process Message Service")
    @MethodSource("provideTestArguments")
    void testProcessMessageService(String messageType) {
        // Test variables:
        String messageId = MESSAGE_ID_PREFIX + messageType;
        String movementId = MOVEMENT_ID_PREFIX + messageType;
        Message testMessage = null;
        TB015CType testTB015CType = null;
        MessageTypes cargoMessageType = MessageTypes.fromValue(messageType);
        // Mocks/Stubs per test case:
        switch (cargoMessageType) {
            case TB_007_C:
            case TB_014_C:
                // TODO: Add needed mocks/stubs when implemented.
                break;
            case TB_015_C:
                testTB015CType = TestDataUtilities.createTB015C(TEST_DEPARTURE_OFFICE, TEST_DESTINATION_OFFICE);
                testMessage = TestDataUtilities.createMessage(messageId, messageType, XmlConverter.marshal(testTB015CType,
                        NamespacesEnum.NTA.getValue(), MessageTypes.TB_015_C.value()));
                when(dossierDataService.getMovementById(movementId)).thenReturn(TestDataUtilities.createMovement(movementId));
                when(dossierDataService.getMessageById(messageId)).thenReturn(testMessage);
                break;
            default:
                break;
        }
        // Call ProcessMessageService method:
        processMessageService.process(messageId, messageType, movementId);
        // Verifications & assertions per test case:
        switch (cargoMessageType) {
            case TB_007_C:
                verify(arrivalNotificationFacade, times(1)).initProcessInstance(movementId, messageId);
                break;
            case TB_014_C:
                verify(invalidationFacade, times(1)).initProcessInstance(movementId, messageId);
                break;
            case TB_015_C:
                verify(dossierDataService, times(1)).getMovementById(movementId);
                verify(dossierDataService, times(1)).getMessageById(messageId);
                verify(dossierDataService, times(1)).createDeclaration(declarationArgumentCaptor.capture());
                verify(dossierDataService, times(1)).updateMovement(movementArgumentCaptor.capture());
                verify(mainFacade, times(1)).initProcessInstance(movementId, messageId);
                Movement capturedMovement = movementArgumentCaptor.getValue();
                Declaration capturedDeclaration = declarationArgumentCaptor.getValue();
                assertEquals(testTB015CType.getTransitOperation().getDeclarationType(), capturedMovement.getDeclarationType());
                assertEquals(testTB015CType.getHolderOfTheTransitProcedure().getIdentificationNumber(), capturedMovement.getSubmitterIdentification());
                assertEquals(testTB015CType.getCustomsOfficeOfDeparture().getReferenceNumber(), capturedMovement.getOfficeOfDeparture());
                assertEquals(testTB015CType.getCustomsOfficeOfDestinationDeclared().getReferenceNumber(), capturedMovement.getOfficeOfDestination());
                assertEquals(testTB015CType.getConsignment().getCountryOfDestination(), capturedMovement.getCountryOfDestination());
                assertEquals(movementId, capturedDeclaration.getMovementId());
                assertEquals(messageType, capturedDeclaration.getPayloadType());
                assertEquals(testMessage.getPayload(), capturedDeclaration.getPayload());
                break;
            default:
                // Invalid Cargo Message type provided as test argument.
                fail();
                break;
        }
    }

    private static Stream<Arguments> provideTestArguments() {
        return Stream.of(
                Arguments.of(MessageTypes.TB_007_C.value()),
                Arguments.of(MessageTypes.TB_014_C.value()),
                Arguments.of(MessageTypes.TB_015_C.value())
        );
    }
}
