package com.intrasoft.ermis.transit.officeofdeparture.service;

import static com.intrasoft.ermis.transit.common.util.TransitReflectionUtil.getCurrentMethod;

import com.intrasoft.ermis.common.bpmn.core.BpmnEngine;
import com.intrasoft.ermis.common.logging.ErmisLogger;
import com.intrasoft.ermis.common.xml.parsing.XmlConverter;
import com.intrasoft.ermis.ddntav5152.messages.CC013CType;
import com.intrasoft.ermis.ddntav5152.messages.CC014CType;
import com.intrasoft.ermis.ddntav5152.messages.CC015CType;
import com.intrasoft.ermis.ddntav5152.messages.CD050CType;
import com.intrasoft.ermis.ddntav5152.messages.CD115CType;
import com.intrasoft.ermis.ddntav5152.messages.CD118CType;
import com.intrasoft.ermis.ddntav5152.messages.CD165CType;
import com.intrasoft.ermis.ddntav5152.messages.MessageTypes;
import com.intrasoft.ermis.platform.shared.client.configuration.core.domain.Configuration;
import com.intrasoft.ermis.transit.bpmnshared.BpmnManagementService;
import com.intrasoft.ermis.transit.common.config.services.ErmisConfigurationService;
import com.intrasoft.ermis.transit.common.enums.MessageOfficeTypeExpectedStatusesEnum;
import com.intrasoft.ermis.transit.common.enums.MessageStatusEnum;
import com.intrasoft.ermis.transit.common.enums.OfficeRoleTypesEnum;
import com.intrasoft.ermis.transit.common.enums.ProcessingStatusTypeEnum;
import com.intrasoft.ermis.transit.common.exceptions.BaseException;
import com.intrasoft.ermis.transit.common.exceptions.MessageOutOfOrderException;
import com.intrasoft.ermis.transit.common.exceptions.NotFoundException;
import com.intrasoft.ermis.transit.common.util.MessageUtil;
import com.intrasoft.ermis.transit.contracts.core.enums.MessageXPathsEnum;
import com.intrasoft.ermis.transit.contracts.core.enums.YesNoEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Declaration;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Message;
import com.intrasoft.ermis.transit.officeofdeparture.domain.MessageOverview;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Movement;
import com.intrasoft.ermis.transit.officeofdeparture.domain.ValidationResult;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.BpmnFlowMessagesEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.BpmnTimersEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.ProcessDefinitionEnum;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureFallbackDeclarationFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleAmendmentRequestFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleDestinationControlResultsFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleDiversionFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleIncidentNotificationFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleOfArrivalAdviceFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleRecoveryFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureInitializationFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureInvalidationFacade;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.bind.JAXBException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.xml.sax.InputSource;

@Service
@RequiredArgsConstructor
public class HandleDDNTAMessageServiceImpl implements HandleDDNTAMessageService {

    public static final String STATE_VALIDATION_FAILED = "State validation failed";

    public static final String IS_SEND_COPY_OF_CD006C_ENABLED_RS = "isSendCopyOfIE006EnabledRS";
    public static final String SEND_COPY_OF_CD006C_ENABLED = "sendCopyOfIE006Enabled";

    private final ErmisLogger log;

    private final MovementService movementService;

    private final DossierDataService dossierDataService;

    private final OoDepartureInitializationFacade ooDepartureInitializationFacade;

    private final OoDepartureInvalidationFacade ooDepartureInvalidationFacade;

    private final OoDepartureHandleOfArrivalAdviceFacade ooDepartureHandleOfArrivalAdviceFacade;

    private final OoDepartureHandleDestinationControlResultsFacade ooDepartureHandleDestinationControlResultsFacade;

    private final OoDepartureHandleAmendmentRequestFacade ooDepartureHandleAmendmentRequestFacade;

    private final OoDepartureHandleDiversionFacade ooDepartureHandleDiversionFacade;

    private final OoDepartureHandleIncidentNotificationFacade ooDepartureHandleIncidentNotificationFacade;

    private final OoDepartureHandleRecoveryFacade ooDepartureHandleRecoveryFacade;

    private final OoDepartureFallbackDeclarationFacade ooDepartureFallbackDeclarationFacade;

    private final GuaranteeService guaranteeService;

    private final InvalidationService invalidationService;

    private final BpmnManagementService bpmnManagementService;

    private final GenerateMessageService generateMessageService;

    private final InitializationService initializationService;

    private final EnquiryService enquiryService;

    private final FallbackDeclarationService fallbackDeclarationService;

    private final ProcessingStatusService processingStatusService;

    private final AmendmentRequestService amendmentRequestService;

    private final BpmnEngine bpmnEngine;

    private final ErmisConfigurationService ermisConfigurationService;

    public void handleDDNTAMessage(Movement movement, String movementId, String messageId, String messageType)
            throws MessageOutOfOrderException, JAXBException {

        MessageTypes messageTypeEnum = MessageTypes.fromValue(messageType);

        switch (messageTypeEnum) {
            case CD_006_C:
                movementService.checkForNullMovement(movement);
                processCD006C(movement, messageId);
                break;
            case CD_018_C:
                movementService.checkForNullMovement(movement);
                processCD018C(movement, messageId);
                break;
            case CD_027_C:
                movementService.checkForNullMovement(movement);
                processCD027C(movement, messageId);
                break;
            case CD_095_C:
                processCD095C(movementId, messageId);
                break;
            case CD_118_C:
                movementService.checkForNullMovement(movement);
                processCD118C(movement, messageId);
                break;
            case CD_143_C:
                movementService.checkForNullMovement(movement);
                processCD143C(movement, messageId);
                break;
            case CD_145_C:
                enquiryService.handleIE145Message(movementId, messageId);
                break;
            case CD_150_C:
                movementService.checkForNullMovement(movement);
                processCD150C(movement, messageId);
                break;
            case CD_151_C:
                movementService.checkForNullMovement(movement);
                processCD151C(movement, messageId);
                break;
            case CD_152_C:
                movementService.checkForNullMovement(movement);
                processCD152C(movement, messageId);
                break;
            case CD_168_C:
                movementService.checkForNullMovement(movement);
                processCD168C(movement, messageId);
                break;
            case CD_180_C:
                processCD180C(movementId, messageId);
                break;
            case CD_205_C:
                guaranteeService.onGuaranteeUseRegistrationCompleted(movementId, messageId);
                break;
            case CD_002_C:
            case CD_114_C:
            case CD_164_C:
                ooDepartureHandleDiversionFacade.initProcessInstance(movementId, messageId, messageType);
                break;
            case CC_013_C:
                movementService.checkForNullMovement(movement);
                processCC013C(movement, messageId);
                break;
            case CC_014_C:
                movementService.checkForNullMovement(movement);
                processCC014(movement, messageId);
                break;
            case CC_015_C:
                movementService.checkForNullMovement(movement);
                processCC015(movement, messageId);
                break;
            case CC_141_C:
                movementService.checkForNullMovement(movement);
                processCC141C(movement, messageId);
                break;
            case CC_170_C:
                ooDepartureInitializationFacade.handleIE170Message(movementId, messageId);
                break;
            case CC_191_C:
                initializationService.handleIE191Message(movementId, messageId);
                break;
            case CD_974_C:
                movementService.checkForNullMovement(movement);
                processCD974(movementId,messageId);
                break;
            default:
                log.warn("Received unhandled message type: {}. Message ignored.", messageType);
                break;
        }
    }

    // CD Message method:
    private void processCD006C(Movement movement, String messageId) {

        // TODO the states Enquiry Recommended and Under Enquiry Procedure
        // for the Office of Departure are not yet assignable in the current flow

        ProcessingStatusTypeEnum movementCurrentState = movementService.checkLatestState(movement.getId());
        boolean isInStateMovementReleased = ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED == movementCurrentState;
        boolean recoveryRecommendedTimerIsActive = isRecoveryRecommendedTimerActive(movement);

        // ("Enquiry Recommended" OR "Under Enquiry Procedure OR Recovery Recommended")
        // AND NO IE006 has been received (only one (1) IE006 associated with Movement):
        boolean conditionalStatesValidWithNo006 =
                (ProcessingStatusTypeEnum.OODEP_ENQUIRY_RECOMMENDED == movementCurrentState
                        || ProcessingStatusTypeEnum.OODEP_UNDER_ENQUIRY_PROCEDURE == movementCurrentState
                        || ProcessingStatusTypeEnum.OODEP_RECOVERY_RECOMMENDED == movementCurrentState
                ) && (dossierDataService.getMessagesOverview(movement.getId(), MessageTypes.CD_006_C.value()).size() == 1);

        if (isInStateMovementReleased || conditionalStatesValidWithNo006) {
            if (recoveryRecommendedTimerIsActive) {
                updateCountryAndOfficeOfDestinationOfMovement(movement, messageId);
                invalidationService.cancelInvalidationRequestIfExists(movement.getId());
                ooDepartureHandleOfArrivalAdviceFacade.initProcessInstance(movement, messageId);
            } else {
                throw new MessageOutOfOrderException(getCurrentMethod(), "T_Recovery_Recommended timer is not Active");
            }
        } else {
            throw new MessageOutOfOrderException(getCurrentMethod(),
                    STATE_VALIDATION_FAILED + " -> NOT (isInStateMovementReleased || conditionalStatesValidWithNo006)");
        }

        boolean sendCopyOfCD006CMessage = false;
        try {
            Configuration configuration = ermisConfigurationService.getConfigurationByTemplateName(IS_SEND_COPY_OF_CD006C_ENABLED_RS);
            sendCopyOfCD006CMessage = Boolean.parseBoolean(configuration.getEvaluationParams().get(SEND_COPY_OF_CD006C_ENABLED));
        } catch (NotFoundException e) {
            log.warn(e.getMessage().concat(". Defaulting to false."));
        }
        if (sendCopyOfCD006CMessage) {
            log.info("Going to send a copy of CD006C message.");
            generateMessageService.generateCopyOfIE006(movement.getId(), messageId);
        } else {
            log.info("Configuration value is false. Won't send a copy of CD006C message.");
        }
    }

    private void processCD018C(Movement movement, String messageId) throws MessageOutOfOrderException {

        // TODO The states Enquiry Recommended and Under Enquiry Procedure
        // for the Office of Departure are not yet assignable in the current flow

        Message message = dossierDataService.getMessageById(messageId);
        ProcessingStatusTypeEnum movementCurrentState = movementService.checkLatestState(movement.getId());

        boolean isInStateMovementReleased = ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED == movementCurrentState;
        boolean isInStateArrived = ProcessingStatusTypeEnum.OODEP_ARRIVED == movementCurrentState;
        boolean ie006HasBeenReceived = dossierDataService.messageTypeExistsForMovement(movement.getId(), MessageTypes.CD_006_C);
        boolean recoveryRecommendedTimerIsActive = isRecoveryRecommendedTimerActive(movement);

        // ("Enquiry Recommended" OR "Under Enquiry Procedure" OR "Recovery Recommended") AND IE006 has been received:
        boolean conditionalStatesValidWithExisting006 =
                (ProcessingStatusTypeEnum.OODEP_ENQUIRY_RECOMMENDED == movementCurrentState
                        || ProcessingStatusTypeEnum.OODEP_UNDER_ENQUIRY_PROCEDURE == movementCurrentState
                        || ProcessingStatusTypeEnum.OODEP_RECOVERY_RECOMMENDED == movementCurrentState
                ) && ie006HasBeenReceived;

        // ("Enquiry Recommended" OR "Under Enquiry Procedure") AND IE006 !!HAS NOT!! been received:
        boolean conditionalStatesValidWithout006 =
                (ProcessingStatusTypeEnum.OODEP_ENQUIRY_RECOMMENDED == movementCurrentState
                        || ProcessingStatusTypeEnum.OODEP_UNDER_ENQUIRY_PROCEDURE == movementCurrentState
                ) && !ie006HasBeenReceived;

        if (((isInStateArrived || conditionalStatesValidWithExisting006) && !message.isManualInserted())
                || (message.isManualInserted() && (isInStateMovementReleased || conditionalStatesValidWithout006))) {
            if (recoveryRecommendedTimerIsActive) {
                // Check if process is already running for Movement's IE018:
                // (NOTE that for the given process, MRN is set as the processBusinessKey.)
                boolean isProcessInstanceAlreadyRunning = bpmnEngine.isProcessInstanceAlreadyRunning(
                        ProcessDefinitionEnum.OODEPARTURE_HANDLE_DESTINATION_CONTROL_RESULTS.getProcessDefinitionKey(),
                        movement.getMrn(),
                        null);
                if (isProcessInstanceAlreadyRunning) {
                    throw new MessageOutOfOrderException(getCurrentMethod(), STATE_VALIDATION_FAILED);
                } else {
                    updateCountryAndOfficeOfDestinationOfMovement(movement, messageId);
                    // If not already running, start it:
                    ooDepartureHandleDestinationControlResultsFacade.initProcessInstance(movement, messageId);
                }
            } else {
                throw new MessageOutOfOrderException(getCurrentMethod(), "T_Recovery_Recommended timer is not Active");
            }
        } else {
            throw new MessageOutOfOrderException(getCurrentMethod(), STATE_VALIDATION_FAILED);
        }
    }

    /**
     * Handles CD027 for known movement
     *
     * @param movement
     * @param messageId
     * @throws JAXBException
     */
    private void processCD027C(Movement movement, String messageId) throws JAXBException {
        log.info("processCD027C() start...");
        generateMessageService.generateIE038(movement.getId(), messageId);
        log.info("processCD027C() end : Generated IE038");
    }

    private void processCD095C(String movementId, String messageId) {
        // Simple logger to verify arrival at office of Departure:
        log.info("IE095 with Movement ID: {} and Message ID: {} received!", movementId, messageId);
    }

    private void processCD118C(Movement movement, String messageId) {

        CD118CType cd118cMessage = dossierDataService.getDDNTAMessageById(messageId, CD118CType.class);
        // "Movement Released" OR...
        boolean movementReleased = movementService.checkIfInGivenState(movement.getId(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED);
        // ("Enquiry Recommended" OR "Under Enquiry Procedure") AND NO IE006 has been received:
        boolean enquiryStateValid =
                (movementService.checkIfInGivenState(movement.getId(), ProcessingStatusTypeEnum.OODEP_ENQUIRY_RECOMMENDED)
                        || movementService.checkIfInGivenState(movement.getId(), ProcessingStatusTypeEnum.OODEP_UNDER_ENQUIRY_PROCEDURE))
                        && dossierDataService.getMessagesOverview(movement.getId(), MessageTypes.CD_006_C.value()).isEmpty();

        if (movementReleased || enquiryStateValid) {

            List<String> ie050IdList = dossierDataService.getMessagesOverview(movement.getId(), MessageTypes.CD_050_C.value())
                                                         .stream()
                                                         .map(MessageOverview::getId)
                                                         .collect(Collectors.toList());

            List<String> ie115IdList = dossierDataService.getMessagesOverview(movement.getId(), MessageTypes.CD_115_C.value())
                                                         .stream()
                                                         .map(MessageOverview::getId)
                                                         .collect(Collectors.toList());

            List<String> ie118IdList = dossierDataService.getMessagesOverview(movement.getId(), MessageTypes.CD_118_C.value())
                                                         .stream()
                                                         .map(MessageOverview::getId)
                                                         .collect(Collectors.toList());
            // IE050 OR...
            boolean ie050Sent = checkIE050SentToCountry(ie050IdList, cd118cMessage.getCustomsOfficeOfTransitActual().getReferenceNumber());
            // IE115 have been sent AND...
            boolean ie115Sent = checkIE115SentToCountry(ie115IdList, cd118cMessage.getCustomsOfficeOfTransitActual().getReferenceNumber());
            // NO other IE118 has been received from that Office - list must have ONE item per Transit Office due to association:
            boolean noOtherIE118ReceivedFromTransitActual =
                    checkNoOtherIE118ReceivedFromTransitActual(ie118IdList, cd118cMessage.getCustomsOfficeOfTransitActual().getReferenceNumber());

            if ((ie050Sent || ie115Sent) && noOtherIE118ReceivedFromTransitActual) {
                log.info("Received IE118 by Office of Transit: {}", cd118cMessage.getCustomsOfficeOfTransitActual().getReferenceNumber());
                invalidationService.cancelInvalidationRequestIfExists(movement.getId());
            } else {
                throw new MessageOutOfOrderException(getCurrentMethod(),
                        STATE_VALIDATION_FAILED + " : NOT (ie050Sent && ie115Sent && noOtherIE118ReceivedFromTransitActual)");
            }
        } else {
            throw new MessageOutOfOrderException(getCurrentMethod(), STATE_VALIDATION_FAILED + " : NOT (movementReleased || enquiryStateValid)");
        }
    }

    private void processCD143C(Movement movement, String messageId) {
        enquiryService.handleIE143Message(movement, messageId);
    }

    private void processCD150C(Movement movement, String messageId) {
        // State validation as in Management Microservice
        Set<String> allowedProcessingStatusItemCodes =
                MessageOfficeTypeExpectedStatusesEnum.getProcessingStatusItemCodesByMessageTypeAndRoleType(MessageTypes.CD_150_C,
                        OfficeRoleTypesEnum.DEPARTURE);
        ProcessingStatusTypeEnum currentProcessingStatus = movementService.checkLatestState(movement.getId());

        if (allowedProcessingStatusItemCodes.contains(currentProcessingStatus.getItemCode())) {
            invalidationService.cancelInvalidationRequestIfExists(movement.getId());
            ooDepartureHandleRecoveryFacade.onCD150CMessageReceived(movement, messageId);
        } else {
            throw new MessageOutOfOrderException(getCurrentMethod(),
                    STATE_VALIDATION_FAILED + ": Movement state should be " + allowedProcessingStatusItemCodes + " but is "
                            + movement.getProcessingStatus());
        }
    }

    private void processCD151C(Movement movement, String messageId) {
        boolean isMovementInOoDepRecoveryRecommendedState =
                movementService.checkIfInGivenState(movement.getId(), ProcessingStatusTypeEnum.OODEP_RECOVERY_RECOMMENDED);
        if (isMovementInOoDepRecoveryRecommendedState) {
            ooDepartureHandleRecoveryFacade.onCD151CMessageReceived(movement, messageId);
        } else {
            throw new MessageOutOfOrderException(getCurrentMethod(),
                    STATE_VALIDATION_FAILED + ": Movement state should be " + ProcessingStatusTypeEnum.OODEP_RECOVERY_RECOMMENDED + " but is "
                            + movement.getProcessingStatus());
        }
    }

    private void processCD152C(Movement movement, String messageId) {
        if (movementService.checkIfInGivenState(movement.getId(), ProcessingStatusTypeEnum.OODEP_UNDER_RECOVERY_PROCEDURE)) {
            ooDepartureHandleRecoveryFacade.onCD152CCReceived(movement.getId(), messageId);
        } else {
            throw new MessageOutOfOrderException(getCurrentMethod(),
                    STATE_VALIDATION_FAILED + " : NOT in -> " + ProcessingStatusTypeEnum.OODEP_UNDER_RECOVERY_PROCEDURE + " state");
        }
    }

    private void processCD168C(Movement movement, String messageId) throws MessageOutOfOrderException {

        // Retrieve IE168 Message:
        Message ie168Message = dossierDataService.getMessageById(messageId);
        // Collect distinct NAs where IE160 and/or IE165 (positive) has been sent to.
        // IE160 countries:
        Set<String> countriesSent = dossierDataService.getMessagesOverview(movement.getId(), MessageTypes.CD_160_C.value())
                                                      .stream()
                                                      .map(overview -> overview.getDestination().substring(overview.getDestination().length() - 2))
                                                      .collect(Collectors.toSet());
        // IE165 (positive) countries:
        List<MessageOverview> ie165OverviewList = dossierDataService.getMessagesOverview(movement.getId(), MessageTypes.CD_165_C.value());
        ie165OverviewList.forEach(overview -> {
            CD165CType cd165cType = dossierDataService.getDDNTAMessageById(overview.getId(), CD165CType.class);
            // IE165 is positive if it does not contain a RequestRejectionReasonCode:
            if (StringUtils.isEmpty(cd165cType.getTransitOperation().getRequestRejectionReasonCode())) {
                countriesSent.add(overview.getDestination().substring(overview.getDestination().length() - 2));
            }
        });
        // Send MessageOutOfOrder (IE906) if no IE160 or IE165 (positive) was sent to the NA of the IE168 sender:
        if (!countriesSent.contains(ie168Message.getSource().substring(ie168Message.getSource().length() - 2))) {
            dossierDataService.updateMessageStatus(ie168Message.getId(), MessageStatusEnum.REJECTED.name());
            throw new MessageOutOfOrderException(getCurrentMethod(),
                    STATE_VALIDATION_FAILED + ": National Authority of IE168 sender is invalid");
        }

        // "Movement Released" OR...
        boolean movementReleasedState = movementService.checkIfInGivenState(movement.getId(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED);

        // ("Enquiry Recommended" OR "Under Enquiry Procedure") AND NO IE006 has been received:
        boolean conditionalStatesValidWithout006 =
                (movementService.checkIfInGivenState(movement.getId(), ProcessingStatusTypeEnum.OODEP_ENQUIRY_RECOMMENDED)
                        || movementService.checkIfInGivenState(movement.getId(), ProcessingStatusTypeEnum.OODEP_UNDER_ENQUIRY_PROCEDURE))
                        && dossierDataService.getMessagesOverview(movement.getId(), MessageTypes.CD_006_C.value()).isEmpty();

        if (movementReleasedState || conditionalStatesValidWithout006) {
            invalidationService.cancelInvalidationRequestIfExists(movement.getId());
        } else {
            dossierDataService.updateMessageStatus(ie168Message.getId(), MessageStatusEnum.REJECTED.name());
            throw new MessageOutOfOrderException(getCurrentMethod(),
                    STATE_VALIDATION_FAILED + ": NOT (movementReleasedState || conditionalStatesValidWithout006)");
        }

    }

    private void processCD180C(String movementId, String messageId) {
        invalidationService.cancelInvalidationRequestIfExists(movementId);
        ooDepartureHandleIncidentNotificationFacade.initProcessInstance(movementId, messageId);
    }

    // CC Message methods:
    private void processCC013C(Movement movement, String messageId) throws JAXBException {
        Declaration declaration = dossierDataService.getLatestDeclarationByMovementId(movement.getId());
        boolean isFallbackDeclaration = false;
        if (declaration != null) {
            CC015CType cc015cTypeMessage = XmlConverter.buildMessageFromXML(declaration.getPayload(), CC015CType.class);
            isFallbackDeclaration = fallbackDeclarationService.isFallBackDeclaration(cc015cTypeMessage);
        }
        if (isThereAnAmendmentActiveForCurrentMovement(movement.getMrn(),
                ProcessDefinitionEnum.OODEPARTURE_HANDLE_AMENDMENT_REQUEST_PROCESS.getProcessDefinitionKey())) {
            createValidationResults(messageId, "An active amendment exists for given movement", "N/A", "N/A");
            generateMessageService.generateIE056(movement.getId(), messageId, false, CC013CType.class);
        } else if (isFallbackDeclaration) {
            amendmentRequestService.amendDeclarationAndCreateNewVersion(movement.getId(), messageId, MessageTypes.CC_013_C.value(), true);
        } else {
            ooDepartureHandleAmendmentRequestFacade.initProcessInstance(movement, messageId);
        }
    }

    private void processCC014(Movement movement, String messageId) throws JAXBException {
        CC014CType cc014CType = dossierDataService.getDDNTAMessageById(messageId, CC014CType.class);
        Declaration declaration = dossierDataService.getLatestDeclarationByMovementId(movement.getId());
        boolean isFallbackDeclaration = false;
        if (declaration != null) {
            CC015CType cc015cTypeMessage = XmlConverter.buildMessageFromXML(declaration.getPayload(), CC015CType.class);
            isFallbackDeclaration = fallbackDeclarationService.isFallBackDeclaration(cc015cTypeMessage);
        }

        if (invalidationService.isMovementBeforeRelease(movement)) {
            if (invalidationService.isInvalidationOnDemand(cc014CType)) {
                ooDepartureInvalidationFacade.initInvalidationProcess(movement, messageId, BpmnFlowMessagesEnum.IE014_FROM_CUSTOMS_OFFICER);
            } else  {
                ooDepartureInvalidationFacade.initInvalidationProcess(movement, messageId, BpmnFlowMessagesEnum.IE014_FROM_TRADER);
            }
        } else if (invalidationService.isMovementAfterRelease(movement)) {
            ooDepartureInvalidationFacade.initOnDemandAfterReleaseProcessInstance(movement, messageId);
        } else if (isFallbackDeclaration) {
            processingStatusService.saveProcessingStatus(movement.getId(), ProcessingStatusTypeEnum.OODEP_PAPER_BASED_CANCELLED.getItemCode());
        } else {
                throw new BaseException(String.format("Received manually IE014 in a status(%s) that cannot be processed", movement.getProcessingStatus()));
        }
    }

    private void processCC015(Movement movement, String messageId) throws JAXBException {
        // Ideally this should be done from within bpmn activity
        Message message = dossierDataService.getMessageById(messageId);
        CC015CType cc015CType = XmlConverter.buildMessageFromXML(message.getPayload(), CC015CType.class);

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

        dossierDataService.createDeclaration(declaration);

        updateMovementWithDeclarationData(movement, message);

        if (fallbackDeclarationService.isFallBackDeclaration(cc015CType)) {
            ooDepartureFallbackDeclarationFacade.initProcessInstance(movement, messageId);
        } else {
            ooDepartureInitializationFacade.initProcessInstance(movement, messageId);
        }
    }

    private void processCC141C(Movement movement, String messageId) {
        enquiryService.handleIE141Message(movement, messageId);
    }

    private void processCD974(String movementId, String messageId) throws JAXBException {
        log.info("processCD974() start...");
        generateMessageService.generateIE975(movementId,messageId);
    }


    private void updateMovementWithDeclarationData(Movement movement, Message message) {
        try {
            CC015CType cc015c = XmlConverter.buildMessageFromXML(message.getPayload(), CC015CType.class);

            YesNoEnum simplifiedProcedure = cc015c.getAuthorisation().stream()
                                                  .filter(authorisation -> "C521".equals(authorisation.getType()))
                                                  .findAny()
                                                  .map(authorisationType02 -> YesNoEnum.YES)
                                                  .orElse(YesNoEnum.NO);

            movement.setProcedureType(simplifiedProcedure.getValue());
            movement.setDeclarationType(cc015c.getTransitOperation().getDeclarationType());
            movement.setSubmitterIdentification(cc015c.getHolderOfTheTransitProcedure().getIdentificationNumber());
            movement.setSecurity(cc015c.getTransitOperation().getSecurity());
            movement.setOfficeOfDeparture(cc015c.getCustomsOfficeOfDeparture().getReferenceNumber());
            movement.setOfficeOfDestination(cc015c.getCustomsOfficeOfDestinationDeclared().getReferenceNumber());
            movement.setCountryOfDestination(cc015c.getConsignment().getCountryOfDestination());
            movement.setCountryOfDispatch(cc015c.getConsignment().getCountryOfDispatch());

            dossierDataService.updateMovement(movement);
            log.info(String.format("Updated details for movement %s", movement.getId()));
        } catch (JAXBException e) {
            log.error("Could not unmarshall message", e);
        }
    }

    private boolean checkIE050SentToCountry(List<String> ie050IdList, String ie118OfficeOfTransitActualReferenceNumber) {

        // Two (2) first alphanumerics of an Office represent the Country it belongs to:
        String country = MessageUtil.extractOfficeCountryCode(ie118OfficeOfTransitActualReferenceNumber);

        return ie050IdList.stream().anyMatch(id -> {

            try {

                Message message = dossierDataService.getMessageById(id);
                CD050CType cd050cMessage = XmlConverter.buildMessageFromXML(message.getPayload(), CD050CType.class);

                return cd050cMessage.getCustomsOfficeOfTransitDeclared().stream().anyMatch(office ->
                        country.equals(MessageUtil.extractOfficeCountryCode(office.getReferenceNumber())));
            } catch (Exception e) {

                log.error("checkIE050Sent() ERROR for messageId: {}", id);
                return false;
            }
        });
    }

    private boolean checkIE115SentToCountry(List<String> ie115IdList, String ie118OfficeOfTransitActualReferenceNumber) {

        // Two (2) first alphanumerics of an Office represent the Country it belongs to:
        String country = MessageUtil.extractOfficeCountryCode(ie118OfficeOfTransitActualReferenceNumber);

        return ie115IdList.stream().anyMatch(id -> {

            try {

                Message message = dossierDataService.getMessageById(id);
                CD115CType cd115cMessage = XmlConverter.buildMessageFromXML(message.getPayload(), CD115CType.class);

                return country.equals(MessageUtil.extractOfficeCountryCode(cd115cMessage.getCustomsOfficeOfTransitActual().getReferenceNumber()));
            } catch (Exception e) {

                log.error("checkIE115Sent() ERROR for messageId: {}", id);
                return false;
            }
        });
    }

    private boolean checkNoOtherIE118ReceivedFromTransitActual(List<String> ie118IdList, String ie118OfficeOfTransitActualReferenceNumber) {

        // Due to association by Management, list of size ONE means it's the first IE118 from this Office of Transit:
        return ie118IdList.stream().filter(id -> {

            try {

                Message message = dossierDataService.getMessageById(id);
                CD118CType cd118cMessage = XmlConverter.buildMessageFromXML(message.getPayload(), CD118CType.class);

                return ie118OfficeOfTransitActualReferenceNumber.equals(cd118cMessage.getCustomsOfficeOfTransitActual().getReferenceNumber());
            } catch (Exception e) {

                log.error("checkNoOtherIE118ReceivedFromTransitActual() ERROR for messageId: {}", id);
                return false;
            }
        }).count() == 1;
    }

    private boolean isThereAnAmendmentActiveForCurrentMovement(String businessKey, String processDefinition) {
        return !bpmnManagementService.findProcessInstanceByBusinessKeyAndProcessDefinition(businessKey, processDefinition).isEmpty();
    }

    private void createValidationResults(String messageId, String ruleId, String pointer, String errorCode) {
        List<ValidationResult> validationResults = new ArrayList<>();

        ValidationResult validationResult = new ValidationResult();
        validationResult.setMessageId(messageId);
        validationResult.setRuleId(ruleId);
        validationResult.setPointer(pointer);
        validationResult.setErrorCode(errorCode);
        validationResult.setRejecting(true);

        validationResults.add(validationResult);

        dossierDataService.saveValidationResults(validationResults);
    }

    private boolean isRecoveryRecommendedTimerActive(Movement movement) {
        return bpmnManagementService.isTimerActive(
                movement.getMrn(),
                ProcessDefinitionEnum.OODEPARTURE_RELEASE_TRANSIT_PROCESS.getProcessDefinitionKey(),
                BpmnTimersEnum.RECOVERY_RECOMMENDED_TIMER.getActivityId());
    }

    @SuppressWarnings("findsecbugs:XPATH_INJECTION")
    private void updateCountryAndOfficeOfDestinationOfMovement(Movement movement, String messageId) {

        Message message = dossierDataService.getMessageById(messageId);

        try (StringReader sr = new StringReader(message.getPayload())) {
            InputSource xml = new InputSource(sr);
            XPath xPath = XPathFactory.newInstance().newXPath();
            String actualCustomsOfficeOfDestination = (String) xPath.evaluate(MessageXPathsEnum.DESTINATION_ACTUAL_OFFICE_ID.getValue(), xml, XPathConstants.STRING);
            String actualCountryOfDestination = MessageUtil.extractOfficeCountryCode(actualCustomsOfficeOfDestination);

            movement.setOfficeOfDestination(actualCustomsOfficeOfDestination);
            movement.setCountryOfDestination(actualCountryOfDestination);
            dossierDataService.updateMovement(movement);
        } catch (XPathExpressionException exception) {
            log.error("Error getting xpath for expression: " + MessageXPathsEnum.DESTINATION_ACTUAL_OFFICE_ID.getValue() + " from message: " + message.getMessageType());
        }


    }
}

package com.intrasoft.ermis.trs.officeofdeparture.service;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.intrasoft.ermis.common.logging.ErmisLogger;
import com.intrasoft.ermis.ddntav5152.messages.CD165CType;
import com.intrasoft.ermis.ddntav5152.messages.MessageTypes;
import com.intrasoft.ermis.ddntav5152.messages.TransitOperationType41;
import com.intrasoft.ermis.transit.bpmnshared.BpmnManagementService;
import com.intrasoft.ermis.transit.common.exceptions.MessageOutOfOrderException;
import com.intrasoft.ermis.transit.common.exceptions.NotFoundException;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Message;
import com.intrasoft.ermis.transit.officeofdeparture.domain.MessageOverview;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Movement;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureFallbackDeclarationFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleAmendmentRequestFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleDestinationControlResultsFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleDiversionFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleEnquiryProcessFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleIncidentNotificationFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleOfArrivalAdviceFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureHandleRecoveryFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureInitializationFacade;
import com.intrasoft.ermis.transit.officeofdeparture.process.OoDepartureInvalidationFacade;
import com.intrasoft.ermis.transit.officeofdeparture.service.AmendmentRequestService;
import com.intrasoft.ermis.transit.officeofdeparture.service.CustomsOfficerActionService;
import com.intrasoft.ermis.transit.officeofdeparture.service.DossierDataService;
import com.intrasoft.ermis.transit.officeofdeparture.service.EnquiryService;
import com.intrasoft.ermis.transit.officeofdeparture.service.FallbackDeclarationService;
import com.intrasoft.ermis.transit.officeofdeparture.service.GenerateMessageService;
import com.intrasoft.ermis.transit.officeofdeparture.service.GuaranteeService;
import com.intrasoft.ermis.transit.officeofdeparture.service.HandleDDNTAMessageServiceImpl;
import com.intrasoft.ermis.transit.officeofdeparture.service.InitializationService;
import com.intrasoft.ermis.transit.officeofdeparture.service.InvalidationService;
import com.intrasoft.ermis.transit.officeofdeparture.service.MovementService;
import com.intrasoft.ermis.transit.officeofdeparture.service.ProcessingStatusService;
import com.intrasoft.ermis.transit.officeofdeparture.service.RiskControlAssessmentService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.JAXBException;
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.CsvSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class HandleDDNTAMessageServiceUT {

    private static final String MOVEMENT_ID = "TEST_MOVEMENT_ID";

    private static final String IE168_MESSAGE_ID = "TEST_IE168_MESSAGE_ID";

    private static final String TEST_NA = "NTA.GB";

    @Mock
    private ErmisLogger log;

    @Mock
    private MovementService movementService;

    @Mock
    private DossierDataService dossierDataService;

    @Mock
    private OoDepartureInitializationFacade ooDepartureInitializationFacade;

    @Mock
    private OoDepartureInvalidationFacade ooDepartureInvalidationFacade;

    @Mock
    private OoDepartureHandleOfArrivalAdviceFacade ooDepartureHandleOfArrivalAdviceFacade;

    @Mock
    private OoDepartureHandleDestinationControlResultsFacade ooDepartureHandleDestinationControlResultsFacade;

    @Mock
    private OoDepartureHandleAmendmentRequestFacade ooDepartureHandleAmendmentRequestFacade;

    @Mock
    private OoDepartureHandleDiversionFacade ooDepartureHandleDiversionFacade;

    @Mock
    private OoDepartureHandleIncidentNotificationFacade ooDepartureHandleIncidentNotificationFacade;

    @Mock
    private OoDepartureHandleRecoveryFacade ooDepartureHandleRecoveryFacade;

    @Mock
    private OoDepartureFallbackDeclarationFacade ooDepartureFallbackDeclarationFacade;

    @Mock
    private GuaranteeService guaranteeService;

    @Mock
    private InvalidationService invalidationService;

    @Mock
    private BpmnManagementService bpmnManagementService;

    @Mock
    private GenerateMessageService generateMessageService;

    @Mock
    private InitializationService initializationService;

    @Mock
    private EnquiryService enquiryService;

    @Mock
    private FallbackDeclarationService fallbackDeclarationService;

    @Mock
    private ProcessingStatusService processingStatusService;

    @Mock
    private AmendmentRequestService amendmentRequestService;

    @InjectMocks
    private HandleDDNTAMessageServiceImpl handleDDNTAMessageService;

    @ParameterizedTest
    @DisplayName("Test - Process IE168 - National Authority Checks")
    @CsvSource({"true,true", "true,false", "false,true", "false,false",})
    void testProcessIE168NationalAuthorityChecks(boolean sentIE160, boolean sentIE165Positive) {
        // Test data:
        Movement testMovement = Movement.builder().id(MOVEMENT_ID).build();
        // Stubs:
        // General:
        if (!sentIE160 && !sentIE165Positive) {
            when(dossierDataService.updateMessageStatus(any(), any())).thenReturn(new Message());
        }
        // Unrelated to IE168 case tested:
        if (sentIE160 || sentIE165Positive) {
            when(movementService.checkIfInGivenState(eq(MOVEMENT_ID), any())).thenReturn(true);
            when(dossierDataService.getMessagesOverview(MOVEMENT_ID, MessageTypes.CD_006_C.value())).thenReturn(Collections.emptyList());
        }
        // Related to test case:
        when(dossierDataService.getMessageById(IE168_MESSAGE_ID)).thenReturn(createIE168Message());
        when(dossierDataService.getMessagesOverview(MOVEMENT_ID, MessageTypes.CD_160_C.value())).thenReturn(createIE160MessageOverviewList(sentIE160));
        when(dossierDataService.getMessagesOverview(MOVEMENT_ID, MessageTypes.CD_165_C.value())).thenReturn(createIE165MessageOverviewList(sentIE165Positive));
        when(dossierDataService.getDDNTAMessageById("IE165_DK", CD165CType.class)).thenReturn(createCD165CType(false));
        if (sentIE165Positive) {
            when(dossierDataService.getDDNTAMessageById("IE165_GB", CD165CType.class)).thenReturn(createCD165CType(true));
        }
        // Call tested method:
        try {
            // Verifications:
            if (!sentIE160 && !sentIE165Positive) {
                assertThrows(MessageOutOfOrderException.class,
                        () -> handleDDNTAMessageService.handleDDNTAMessage(testMovement, MOVEMENT_ID, IE168_MESSAGE_ID, MessageTypes.CD_168_C.value()));
            } else {
                handleDDNTAMessageService.handleDDNTAMessage(testMovement, MOVEMENT_ID, IE168_MESSAGE_ID, MessageTypes.CD_168_C.value());
            }
        } catch (JAXBException ex) {
            fail("Method execution failed.");
        }
    }

    private Message createIE168Message() {

        Message ie168Message = new Message();
        ie168Message.setSource(TEST_NA);

        return ie168Message;
    }

    private List<MessageOverview> createIE160MessageOverviewList(boolean sentIE160) {

        List<MessageOverview> messageOverviewList = new ArrayList<>();

        messageOverviewList.add(MessageOverview.builder().destination("NTA.GR").build());
        if (sentIE160) {
            messageOverviewList.add(MessageOverview.builder().destination(TEST_NA).build());
        }

        return messageOverviewList;
    }

    private List<MessageOverview> createIE165MessageOverviewList(boolean sentIE165Positive) {

        List<MessageOverview> messageOverviewList = new ArrayList<>();

        messageOverviewList.add(MessageOverview.builder().id("IE165_DK").destination("NTA.DK").build());
        if (sentIE165Positive) {
            messageOverviewList.add(MessageOverview.builder().id("IE165_GB").destination(TEST_NA).build());
        }

        return messageOverviewList;
    }

    private CD165CType createCD165CType(boolean ie165Positive) {

        CD165CType cd165cType = new CD165CType();
        cd165cType.setTransitOperation(new TransitOperationType41());
        if (!ie165Positive) {
            cd165cType.getTransitOperation().setRequestRejectionReasonCode("REJECT");
        }

        return cd165cType;
    }
}
