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

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.CC044CType;
import com.intrasoft.ermis.ddntav5152.messages.CD018CType;
import com.intrasoft.ermis.ddntav5152.messages.CD150CType;
import com.intrasoft.ermis.ddntav5152.messages.CD151CType;
import com.intrasoft.ermis.ddntav5152.messages.MessageTypes;
import com.intrasoft.ermis.transit.common.config.services.GetCountryCodeService;
import com.intrasoft.ermis.transit.common.enums.DirectionEnum;
import com.intrasoft.ermis.transit.common.enums.EnquiryStatusEnum;
import com.intrasoft.ermis.transit.common.enums.InvalidationExpectedStatusesEnum;
import com.intrasoft.ermis.transit.common.enums.OfficeRoleTypesEnum;
import com.intrasoft.ermis.transit.common.enums.ProcessingStatusTypeEnum;
import com.intrasoft.ermis.transit.common.transition.period.TransitionPeriodService;
import com.intrasoft.ermis.transit.common.util.MessageUtil;
import com.intrasoft.ermis.transit.contracts.core.enums.YesNoEnum;
import com.intrasoft.ermis.transit.contracts.validation.enums.ErrorCode;
import com.intrasoft.ermis.transit.management.core.port.outbound.DossierDataService;
import com.intrasoft.ermis.transit.management.core.util.ValidationHelper;
import com.intrasoft.ermis.transit.management.core.util.ViolationUtils;
import com.intrasoft.ermis.transit.management.domain.Message;
import com.intrasoft.ermis.transit.management.domain.MessageOverview;
import com.intrasoft.ermis.transit.management.domain.Movement;
import com.intrasoft.ermis.transit.management.domain.Violation;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.xml.bind.JAXBException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

/**
 * <pre>
 * Any Message Validation "Custom logic" can be placed within this class
 * Mainly logic that needs το Validate any Incoming DDNTA message type against existing relevant data in the Dossier Database (e.g. for the same MRN)
 * Not intended by any means to call the Validation MicroService via the {@link com.intrasoft.ermis.transit.management.core.port.outbound.ValidationService}
 */


@Service
@RequiredArgsConstructor
public class DDNTAMessageCustomValidationServiceImpl {

    public static final String MRN_PATH = "/%s/TransitOperation/MRN";

    public static final String MESSAGE_RECIPIENT_PATH = "/messageRecipient";

    public static final String MESSAGE_GROSS_MASS_PATH = "/Consignment/HouseConsignment/grossMass";

    private static final String XSD_ERROR_REASON = "XSD";

    private static final String INVALID_GROSS_MASS_VALUE = "The value '0' of element 'grossMass' is not valid";

    private final ErmisLogger log;

    private final GetCountryCodeService getCountryCodeService;

    private final TransitionPeriodService transitionPeriodService;


    @FunctionalInterface
    private interface DDNTAMessageCustomValidator {
        List<Violation> validateMessageType(List<Movement> movements, Message message) throws JAXBException;
    }


    private static final Set<MessageTypes> NOT_ALLOWED_IN_INVALIDATION_AFTER_RELEASE = Set.of(
            MessageTypes.CD_006_C, MessageTypes.CD_118_C, MessageTypes.CD_168_C, MessageTypes.CD_180_C);

    private final DossierDataService dossierDataService;

    private final ConfigurationSwitchService configurationSwitchService;

    @SneakyThrows
    protected List<Violation> performCustomValidationPerMessageType(List<Movement> movements, Message message) {
        MessageTypes messageType =
                Objects.requireNonNull(MessageTypes.fromValue(message.getMessageType()), "Invalid/Unknown Message Type: [" + message.getMessageType() + "]");
        List<Violation> violationsList = getDDNTAMessageCustomValidator(messageType).validateMessageType(movements, message);
        if (!violationsList.isEmpty()) {
            log.error(
                    "performCustomValidationPerMessageType() : CUSTOM VALIDATION FAILED for Message Type [{}] with MRN: [{}]",
                    messageType.value(), message.getMrn());
        }
        return violationsList;
    }

    private DDNTAMessageCustomValidator getDDNTAMessageCustomValidator(MessageTypes messageType) {
        switch (messageType) {
            case CD_181_C:
                return this::validateIE181;
            case CD_010_C:
                return this::validateIE010;
            case CD_018_C:
                return this::validateIE018;
            case CD_150_C:
                return this::validateIE150;
            case CD_151_C:
                return this::validateIE151;
            case CD_152_C:
                return this::validateIE152;
            case CC_013_C:
                return this::validateIE013;
            case CC_014_C:
                return this::validateIE014;
            case CC_141_C:
                return this::validateIE141;
            case CD_143_C:
                return this::validateIE143;
            case CC_007_C:
            case CC_015_C:
            case CC_170_C:
                return this::validateMessageDestination;
            case CC_044_C:
                return this::validateIE044;
            default:
                // For rest of message types that do not need Custom Validation, return a dummy implementation
                return (movements, message) -> List.of();
        }
    }

    /**
     * <pre>
     * Custom Validation ( for NL) only for {@link com.intrasoft.ermis.ddntav5152.messages.CC170CType} type and only if the MRN Communication Rule is enabled
     * In that case and if declaration is pre-lodged the received CC170C type should have a required [TransitOperation/MRN] element with a non-empty MRN value
     * It has been implemented as a separate public method because it is being called initially when the IE170 is received and before any attempt to associate
     * the IE170 message to an existing movement
     */
    public List<Violation> validateIE170MrnIfMrnCommunicationIsEnabled(Message message) {
        List<Violation> violations = new ArrayList<>();
        if (MessageTypes.CC_170_C.value().equals(message.getMessageType())) {
            if (configurationSwitchService.isMRNCommunicationInPelodgedEnabled()) {
                if (StringUtils.isBlank(message.getMrn())) {
                    violations.add(
                            ViolationUtils.createViolation(
                                    String.format(MRN_PATH, message.getMessageType()),
                                    MessageUtil.ERROR_REASON_FOR_906,
                                    "",
                                    ErrorCode.MISSING.getCode())
                    );
                }
            } else {
                if (StringUtils.isNotBlank(message.getMrn())) {
                    violations.add(
                            ViolationUtils.createViolation(
                                    String.format(MRN_PATH, message.getMessageType()),
                                    MessageUtil.ERROR_REASON_FOR_906,
                                    message.getMrn(),
                                    ErrorCode.NOT_SUPPORTED_HERE.getCode())
                    );
                }
            }
        }
        return violations;
    }

    /**
     * <pre>
     * **********************************************************************************************************************************
     * Validator Methods per Message Type. These are all Implementations of the {@link DDNTAMessageCustomValidator} functional interface
     * **********************************************************************************************************************************
     */

    private List<Violation> validateIE010(List<Movement> movements, Message message) {
        List<MessageOverview> messageOverviewList =
                dossierDataService.getMessagesOverviewByMrnAndMessageTypes(message.getMrn(), List.of(
                        MessageTypes.CD_180_C,
                        MessageTypes.CD_181_C,
                        MessageTypes.CD_006_C,
                        MessageTypes.CD_118_C,
                        MessageTypes.CD_168_C));
        if (!messageOverviewList.isEmpty()) {
            log.error(
                    "validateIE010() FAILED validation for MRN: [{}] and Message Type: [{}]. Found {} message(s) of type IE180/IE181 for the same MRN",
                    message.getMrn(), MessageTypes.fromValue(message.getMessageType()).value(), messageOverviewList.size());
            messageOverviewList.forEach(
                    messageOverview -> log.warn("\t-> Message type: [{}], direction: [{}], messageId: [{}], id: [{}]",
                            messageOverview.getMessageType(),
                            messageOverview.getDirection(),
                            messageOverview.getMessageIdentification(),
                            messageOverview.getId()));
            return List.of(ViolationUtils.createOutOfOrderViolation(message.getMessageType()));
        }
        return List.of();
    }

    private List<Violation> validateIE018(List<Movement> movements, Message message) throws JAXBException {
        List<Violation> violations = new ArrayList<>();
        CD018CType messagePayload = XmlConverter.buildMessageFromXML(message.getPayload(), CD018CType.class);
        if (transitionPeriodService.isNowNCTSTransitionPeriod() && isGrossMassZeroInCD018C(messagePayload) && message.isManualInserted()) {
            log.error("validate IE018() : Invalid value '0' in gross mass");
            violations.add(ViolationUtils.createViolation(message.getMessageType().concat(MESSAGE_GROSS_MASS_PATH),
                    INVALID_GROSS_MASS_VALUE,
                    XSD_ERROR_REASON,
                    ErrorCode.INVALID_XML_FORMAT.getCode()));
        }
        return violations;
    }

    private boolean isGrossMassZeroInCD018C(CD018CType message) {
        if (message == null  || message.getConsignment() == null || message.getConsignment().getHouseConsignment() == null) {
            return false;
        }

        return message.getConsignment()
                      .getHouseConsignment()
                      .stream()
                      .anyMatch(cd018HouseConsignment -> cd018HouseConsignment.getGrossMass() != null && BigDecimal.ZERO.compareTo(cd018HouseConsignment.getGrossMass()) == 0);
    }

    private List<Violation> validateIE152(List<Movement> movements, Message message) {
        for (Movement movement : movements) {
            if (!movement.getOfficeRoleType().equals(OfficeRoleTypesEnum.DEPARTURE.getValue()) && cd151PositiveExists(movement, message)) {
                return List.of(ViolationUtils.createOutOfOrderViolation(message.getMessageType()));
            }
        }
        return List.of();
    }

    //R0520 rule implementation
    private List<Violation> validateIE013(List<Movement> movements, Message message) {
        List<Violation> violations = validateMessageDestination(movements, message);

        CC013CType cc013CTypeMessage = dossierDataService.getDDNTAMessageById(message.getId(), CC013CType.class);
        for (Movement movement : movements) {
            if (OfficeRoleTypesEnum.DEPARTURE.getValue().equals(movement.getOfficeRoleType()) && isAmendmentTypeFlagInvalid(movement, cc013CTypeMessage)) {
                violations.add(ViolationUtils.createViolation(ValidationHelper.AMENDMENT_TYPE_FLAG_POINTER, ValidationHelper.R0520_REASON,
                        cc013CTypeMessage.getTransitOperation().getAmendmentTypeFlag(), ValidationHelper.SEMANTIC_ERROR_CODE));
                return violations;
            }
        }
        return violations;
    }

    private List<Violation> validateIE014(List<Movement> movements, Message message) {
        List<Violation> violations = validateMessageDestination(movements, message);
        for (Movement movement : movements) {
            /* If movement is in 'After Release' state... */
            if (OfficeRoleTypesEnum.DEPARTURE.getValue().equals(movement.getOfficeRoleType())
                    && InvalidationExpectedStatusesEnum.OODEP_AFTER_RELEASE.getOfficeRoleTypeStatuses()
                                                                           .getStatuses()
                                                                           .contains(movement.getProcessingStatus())) {
                violations.addAll(validateIE014AInAfterReleaseState(message, movement));
                return violations;
            }
        }
        // G0101 implementation:
        violations.addAll(applyG0101OnIE014(message));
        return violations;
    }

    private List<Violation> validateIE014AInAfterReleaseState(Message message, Movement movement) {
        if (message.isManualInserted()) {
            List<MessageOverview> messageOverviewList =
                    dossierDataService.getMessagesOverviewByMrnAndMessageTypes(movement.getMrn(), NOT_ALLOWED_IN_INVALIDATION_AFTER_RELEASE);
            if (!messageOverviewList.isEmpty()) {
                return List.of(ViolationUtils.createViolation("/" + message.getMessageType(), "N/A",
                        null, MessageUtil.ERROR_CODE_FOR_906));
            }
            return List.of();
        } else {
            if (configurationSwitchService.isElectronicIE014EnabledInReleaseState()) {
                List<MessageOverview> messageOverviewList =
                        dossierDataService.getMessagesOverviewByMrnAndMessageTypes(movement.getMrn(), NOT_ALLOWED_IN_INVALIDATION_AFTER_RELEASE);
                if (!messageOverviewList.isEmpty() || ProcessingStatusTypeEnum.OODEP_UNDER_ENQUIRY_PROCEDURE.getItemCode()
                                                                                                            .equals(movement.getProcessingStatus())) {
                    return List.of(ViolationUtils.createViolation("/" + message.getMessageType(), "N/A",
                            null, MessageUtil.ERROR_CODE_FOR_906));
                }
                return List.of();
            } else {
                return List.of(ViolationUtils.createViolation("/" + message.getMessageType(), "N/A",
                        null, MessageUtil.ERROR_CODE_FOR_906));
            }
        }
    }

    /**
     * Technical Description: <br/> N/A <br/> <br/> Functional Description: <br/> The value of the Data Item 'Invalidation.initiatedByCustoms' is <br/> ‘0’
     * ('No') when the request to invalidate is initiated by the trader; <br/> The value of the Data Item 'Invalidation.initiatedByCustoms' is <br/> ‘1’ ('Yes')
     * when the request to invalidate is initiated by the customs.
     *
     * @param message Message holding the CC014C XML in its payload.
     * @return List of Violation if G0101 is violated, else empty List.
     */
    private List<Violation> applyG0101OnIE014(Message message) {

        try {

            CC014CType cc014cTypeMessage = XmlConverter.buildMessageFromXML(message.getPayload(), CC014CType.class);

            String errorPointer = "/".concat(message.getMessageType()).concat("/Invalidation/initiatedByCustoms");
            String errorReason = "G0101";

            Predicate<CC014CType> isInitiatedByCustoms = inputMessage -> "1".equals(inputMessage.getInvalidation().getInitiatedByCustoms());
            Predicate<CC014CType> isNotInitiatedByCustoms = inputMessage -> "0".equals(inputMessage.getInvalidation().getInitiatedByCustoms());

            if ((message.isManualInserted() && isNotInitiatedByCustoms.test(cc014cTypeMessage))
                    || (!message.isManualInserted() && isInitiatedByCustoms.test(cc014cTypeMessage))) {
                return List.of(ViolationUtils.createViolation(errorPointer, errorReason, cc014cTypeMessage.getInvalidation().getInitiatedByCustoms(),
                        ValidationHelper.GUIDELINE_ERROR_CODE));
            } else {
                return List.of();
            }
        } catch (JAXBException e) {
            log.warn("applyG0101OnIE014(): Failed to convert XML payload of Message with ID: "
                    .concat(message.getId())
                    .concat(". Will skip G0101.")
            );
            return List.of();
        }
    }

    /**
     * block the National IE150 messages between the same NA
     */
    @SneakyThrows
    private List<Violation> validateIE150(List<Movement> movements, Message message) {
        CD150CType cd150CType = XmlConverter.buildMessageFromXML(message.getPayload(), CD150CType.class);
        String officeRequesting = cd150CType.getCustomsOfficeOfRecoveryRequesting().getReferenceNumber();
        String officeRequested = cd150CType.getCustomsOfficeOfRecoveryRequested().getReferenceNumber();

        if (officeRequested.startsWith(officeRequesting.substring(0, 2))) {
            return List.of(ViolationUtils.createOutOfOrderViolation(message.getMessageType()));
        }
        return List.of();
    }

    @SneakyThrows
    private List<Violation> validateIE151(List<Movement> movements, Message message) {
        final List<MessageOverview> ie150MessageOverviewList =
                dossierDataService.getMessagesOverviewByMrnAndMessageTypes(message.getMrn(), List.of(MessageTypes.CD_150_C)).stream()
                                  .filter(messageOverview ->
                                          Objects.equals(messageOverview.getDirection(),
                                                  DirectionEnum.SENT.getDirection()))
                                  .sorted(Comparator.comparing(MessageOverview::getCreatedDateTime).reversed())
                                  .collect(Collectors.toList());

        // If more than one found log
        if (ie150MessageOverviewList.size() > 1) {
            log.warn(
                    "validateIE151() : For IE151-RECEIVED with MRN: [{}] found more than one IE150-SENT messages ({}). Will peek the last one with id: [{}] and compare the Office Reference Numbers...#$%^&*@! Ids found: {}",
                    message.getMrn(), ie150MessageOverviewList.size(), ie150MessageOverviewList.get(0).getId(),
                    ie150MessageOverviewList.stream().map(MessageOverview::getId).collect(Collectors.toList()));
        }

        if (ie150MessageOverviewList.isEmpty()) {
            // TODO: Check for IE143 with code = 4. Do not raise a Violation Error till final implemantation!
            log.info(
                    "validateIE151() : For IE151-RECEIVED with MRN: [{}] found NONE IE150-SENT messages. Will probably look for IE143 with Code 4 which is NOT yet implemented. TODO!",
                    message.getMrn());
        } else {
            CD150CType cd150CTypeSent = dossierDataService.getDDNTAMessageById(ie150MessageOverviewList.get(0).getId(), CD150CType.class);
            CD151CType cd151CTypeReceived = XmlConverter.buildMessageFromXML(message.getPayload(), CD151CType.class);
            if (!validateReceivedIE151AgainstSentIE150(cd151CTypeReceived, cd150CTypeSent)) {
                return List.of(ViolationUtils.createOutOfOrderViolation(message.getMessageType()));
            }
        }
        return List.of();
    }

    /**
     * PSD-2577 Performs customs validation for IE181 message. If movement exists in office of destination and state not equals to AAR_CREATED, then CD906C
     * message should be sent.
     */
    @SneakyThrows
    private List<Violation> validateIE181(List<Movement> movements, Message message) {
        return movements.stream().filter(movement ->
                OfficeRoleTypesEnum.DESTINATION.getValue()
                                               .equals(movement.getOfficeRoleType()) && !movement
                        .getProcessingStatus()
                        .equals(ProcessingStatusTypeEnum.OODEST_AAR_CREATED.getItemCode())
        ).map(movement -> ViolationUtils.createOutOfOrderViolation(message.getMessageType())).collect(Collectors.toList());
    }

    private List<Violation> validateIE141(List<Movement> movements, Message message) {
        List<Violation> violations = validateMessageDestination(movements, message);
        violations.addAll(validateEnquiryState(message.getMrn(), message.getMessageType()));

        return violations;
    }

    private List<Violation> validateIE143(List<Movement> movements, Message message) {
        return validateEnquiryState(message.getMrn(), message.getMessageType());
    }

    private List<Violation> validateIE044(List<Movement> movements, Message message) throws JAXBException {
        List<Violation> violations = new ArrayList<>();
        CC044CType messagePayload = XmlConverter.buildMessageFromXML(message.getPayload(), CC044CType.class);
        if (transitionPeriodService.isNowNCTSTransitionPeriod() && isGrossMassZeroInCC044C(messagePayload)) {
            log.error("validate IE044() : Invalid value '0' in gross mass");
            violations.add(ViolationUtils.createViolation(message.getMessageType().concat(MESSAGE_GROSS_MASS_PATH),
                    INVALID_GROSS_MASS_VALUE,
                    XSD_ERROR_REASON,
                    ErrorCode.INVALID_XML_FORMAT.getCode()));
        }
        return violations;
    }

    private boolean isGrossMassZeroInCC044C(CC044CType message) {
        if (message == null  || message.getConsignment() == null || message.getConsignment().getHouseConsignment() == null) {
            return false;
        }

        return message.getConsignment()
                      .getHouseConsignment()
                      .stream()
                      .anyMatch(cc044HouseConsignment -> cc044HouseConsignment.getGrossMass() != null && BigDecimal.ZERO.compareTo(cc044HouseConsignment.getGrossMass()) == 0);
    }

    /**
     * For : CC015C, CC013C, CC014C, CC170C, CC141C, CC007C, CC044C Message Recipient should be: NTA.{Operating Country} i.e. NTA.DK (or any one of the
     * accommodating Countries. e.g. IS for Operating Country NO)
     */
    private List<Violation> validateMessageDestination(List<Movement> movements, Message message) {
        List<Violation> violations = new ArrayList<>();
        String destinationCountryCode = MessageUtil.getCountryCodeFromMessageRecipient(message.getDestination());
        String operatingCountryCode = getCountryCodeService.getCountryCode();
        if (!operatingCountryCode.equals(destinationCountryCode) &&
                (message.getMessageType().equals(MessageTypes.CC_007_C.value()) &&
                        !getCountryCodeService.getAccommodatingCountryCodes().contains(destinationCountryCode))
        ) {
            log.error("validateMessageDestination() : Invalid destination country code: {}. Opc: {} , Acc: {} ", destinationCountryCode, operatingCountryCode,
                    getCountryCodeService.getAccommodatingCountryCodes());
            violations.add(ViolationUtils.createViolation("/" + message.getMessageType().concat(MESSAGE_RECIPIENT_PATH),
                    MessageUtil.ERROR_REASON_FOR_906,
                    message.getDestination(),
                    ErrorCode.NOT_SUPPORTED_CODE_VALUE.getCode()));
        }
        return violations;
    }

    // *************************************  Helper Methods ***********************************************************************
    private boolean validateReceivedIE151AgainstSentIE150(CD151CType cd151CTypeReceived, CD150CType cd150CTypeSent) {
        AtomicInteger errorCount = new AtomicInteger();
        String ie151OfficeOfRecoveryRequestingCountry =
                MessageUtil.extractOfficeCountryCode(cd151CTypeReceived.getCustomsOfficeOfRecoveryRequesting().getReferenceNumber());
        String ie150OfficeOfRecoveryRequestingCountry =
                MessageUtil.extractOfficeCountryCode(cd150CTypeSent.getCustomsOfficeOfRecoveryRequesting().getReferenceNumber());
        String ie151OfficeOfRecoveryRequestedCountry =
                MessageUtil.extractOfficeCountryCode(cd151CTypeReceived.getCustomsOfficeOfRecoveryRequested().getReferenceNumber());
        String ie150OfficeOfRecoveryRequestedCountry =
                MessageUtil.extractOfficeCountryCode(cd150CTypeSent.getCustomsOfficeOfRecoveryRequested().getReferenceNumber());
        if (!Objects.equals(ie151OfficeOfRecoveryRequestingCountry, ie150OfficeOfRecoveryRequestingCountry)) {
            errorCount.incrementAndGet();
            log.error(
                    "validateReceivedIE151AgainstSentIE150() : Country Mismatch [{}] <> [{}] between : IE151/CustomsOfficeOfRecoveryRequesting: [{}] and IE150/CustomsOfficeOfRecoveryRequesting: [{}]",
                    ie151OfficeOfRecoveryRequestingCountry, ie150OfficeOfRecoveryRequestingCountry,
                    cd151CTypeReceived.getCustomsOfficeOfRecoveryRequesting().getReferenceNumber(),
                    cd150CTypeSent.getCustomsOfficeOfRecoveryRequesting().getReferenceNumber());
        }
        if (!Objects.equals(ie151OfficeOfRecoveryRequestedCountry, ie150OfficeOfRecoveryRequestedCountry)) {
            errorCount.incrementAndGet();
            log.error(
                    "validateReceivedIE151AgainstSentIE150() : Country Mismatch [{}] <> [{}] between : IE151/CustomsOfficeOfRecoveryRequested: [{}] and IE150/CustomsOfficeOfRecoveryRequested: [{}]",
                    ie151OfficeOfRecoveryRequestedCountry, ie150OfficeOfRecoveryRequestedCountry,
                    cd151CTypeReceived.getCustomsOfficeOfRecoveryRequested().getReferenceNumber(),
                    cd150CTypeSent.getCustomsOfficeOfRecoveryRequested().getReferenceNumber());
        }
        return errorCount.get() == 0;
    }

    private boolean cd151PositiveExists(Movement movement, Message message) {
        List<MessageOverview> messageOverviewList = dossierDataService.getMessagesOverview(MessageTypes.CD_151_C.value(), movement.getId(), false);
        boolean cd151PositiveExists = false;
        if (!messageOverviewList.isEmpty()) {
            for (MessageOverview messageOverview : messageOverviewList) {
                CD151CType cd151CTypeMessage = dossierDataService.getDDNTAMessageById(messageOverview.getId(), CD151CType.class);
                if (YesNoEnum.YES.getValue().equals(cd151CTypeMessage.getRecovery().getRecoveryAcceptance())) {
                    log.error(
                            "cd151PositiveExists() FAILED validation for MRN: [{}] and Incoming Message Type: [{}]. Found POSITIVE \"Recovery Acceptance Notification\" (IE151) message for the same MRN. Message will be rejected.",
                            movement.getMrn(), message.getMessageType());
                    log.error("\t-> IE151 Message found:  direction: [{}], messageId: [{}], id: [{}] on movementId: [{}]",
                            messageOverview.getDirection(), messageOverview.getMessageIdentification(), messageOverview.getId(), movement.getId());
                    cd151PositiveExists = true;
                    break;
                }
            }
        }
        return cd151PositiveExists;
    }

    private boolean isAmendmentTypeFlagInvalid(Movement movement, CC013CType cc013CTypeMessage) {
        return (ProcessingStatusTypeEnum.OODEP_GUARANTEE_UNDER_AMENDMENT.getItemCode().equals(movement.getProcessingStatus()) && !"1".equals(
                cc013CTypeMessage.getTransitOperation().getAmendmentTypeFlag())) || (
                !ProcessingStatusTypeEnum.OODEP_GUARANTEE_UNDER_AMENDMENT.getItemCode().equals(movement.getProcessingStatus()) && "1".equals(
                        cc013CTypeMessage.getTransitOperation().getAmendmentTypeFlag()));

    }

    /**
     * There can only be one (1) Trader Enquiry (IE140), so only accept the IE141 response if: EnquiryStatus is OPEN, meaning a valid IE141 has not yet been
     * received, else status would be CLOSED.
     *
     * @param mrn
     * @param messageType
     * @return
     */
    private List<Violation> validateEnquiryState(String mrn, String messageType) {
        if (dossierDataService.enquiryExistsByMrnAndStatus(mrn, EnquiryStatusEnum.OPEN.getTypeValue(),
                MessageTypes.CD_143_C.value().equals(messageType))) {
            return List.of();
        }

        return List.of(ViolationUtils.createOutOfOrderViolation(messageType));
    }
}

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

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
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.common.xml.enums.NamespacesEnum;
import com.intrasoft.ermis.common.xml.parsing.XmlConverter;
import com.intrasoft.ermis.ddntav5152.messages.CC014CType;
import com.intrasoft.ermis.ddntav5152.messages.CC044CType;
import com.intrasoft.ermis.ddntav5152.messages.CD018CType;
import com.intrasoft.ermis.ddntav5152.messages.MessageTypes;
import com.intrasoft.ermis.platform.shared.client.configuration.core.domain.Configuration;
import com.intrasoft.ermis.transit.common.transition.period.TransitionPeriodService;
import com.intrasoft.ermis.transit.common.config.services.GetCountryCodeService;
import com.intrasoft.ermis.transit.common.enums.InvalidationExpectedStatusesEnum;
import com.intrasoft.ermis.transit.common.enums.OfficeRoleTypesEnum;
import com.intrasoft.ermis.transit.common.enums.ProcessingStatusTypeEnum;
import com.intrasoft.ermis.transit.common.util.MessageUtil;
import com.intrasoft.ermis.transit.management.core.port.outbound.DossierDataService;
import com.intrasoft.ermis.transit.management.core.util.ValidationHelper;
import com.intrasoft.ermis.transit.management.domain.Message;
import com.intrasoft.ermis.transit.management.domain.MessageOverview;
import com.intrasoft.ermis.transit.management.domain.Movement;
import com.intrasoft.ermis.transit.management.domain.Violation;
import com.intrasoft.ermis.transit.shared.testing.util.FileHelpers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@Slf4j
@ExtendWith(MockitoExtension.class)
public class DDNTAMessageCustomValidationServiceImplUT {

    @Mock
    private DossierDataService dossierDataService;

    @Mock
    private ConfigurationSwitchService configurationSwitchService;

    @Mock
    private ErmisLogger logger;

    @Mock
    private GetCountryCodeService getCountryCodeService;

    @Mock
    private TransitionPeriodService transitionPeriodService;

    @InjectMocks
    DDNTAMessageCustomValidationServiceImpl ddntaMessageCustomValidationService;

    @ParameterizedTest
    @MethodSource("namedArguments")
    @SneakyThrows
    void test_IE014CustomValidation(boolean isElectronicallySubmittedEnabled, boolean isManuallyInserted, String existingInvalidMessageType,
            ProcessingStatusTypeEnum processingStatus) {
        String MRN = "22DKFUECYSBTH9ARK2";
        Set<MessageTypes> invalidMessageTypes = Set.of(MessageTypes.CD_006_C, MessageTypes.CD_118_C, MessageTypes.CD_168_C, MessageTypes.CD_180_C);

        List<MessageOverview> invalidMessageOverviewList = new ArrayList<>();
        if(Objects.nonNull(existingInvalidMessageType) && existingInvalidMessageType.matches("CD168C|CD118C|CD006C|CD180C")) {
            invalidMessageOverviewList.add(MessageOverview.builder().messageType(existingInvalidMessageType).build());
        }

        String cc014cMessage = FileHelpers.xmlToString(MessageTypes.CC_014_C.value() + ".xml");

        CC014CType cc014CType = XmlConverter.buildMessageFromXML(cc014cMessage, CC014CType.class);

        if (isManuallyInserted) {
            cc014CType.getInvalidation().setInitiatedByCustoms("1");
        }

        cc014cMessage = XmlConverter.marshal(cc014CType, NamespacesEnum.NTA.getValue(), MessageTypes.CC_014_C.value());

        Message message = Message.builder()
                                 .messageType(MessageTypes.CC_014_C.value())
                                 .destination("NTA.GR")
                                 .manualInserted(isManuallyInserted)
                                 .payload(cc014cMessage)
                                 .build();

        List<Movement> movements = new ArrayList<>();
        movements.add(Movement.builder()
                              .officeRoleType(OfficeRoleTypesEnum.DEPARTURE.getValue())
                              .mrn(MRN)
                              .processingStatus(processingStatus.getItemCode())
                              .build());


        List<Violation> violations = new ArrayList<>();

        when(getCountryCodeService.getCountryCode()).thenReturn("DK");

        if (!isManuallyInserted) {
            when(configurationSwitchService.isElectronicIE014EnabledInReleaseState()).thenReturn(isElectronicallySubmittedEnabled);
        }

        if ((isManuallyInserted || isElectronicallySubmittedEnabled) &&
                !InvalidationExpectedStatusesEnum.OODEP_BEFORE_RELEASE.getOfficeRoleTypeStatuses().getStatuses().contains(processingStatus.getItemCode())) {
            when(dossierDataService.getMessagesOverviewByMrnAndMessageTypes(MRN, invalidMessageTypes)).thenReturn(invalidMessageOverviewList);
        }

        violations = ddntaMessageCustomValidationService.performCustomValidationPerMessageType(movements, message);

        if((InvalidationExpectedStatusesEnum.OODEP_AFTER_RELEASE.getOfficeRoleTypeStatuses().getStatuses().contains(processingStatus.getItemCode()) &&
                (isElectronicallySubmittedEnabled || isManuallyInserted) && Objects.isNull(existingInvalidMessageType) &&
                !ProcessingStatusTypeEnum.OODEP_UNDER_ENQUIRY_PROCEDURE.equals(processingStatus)) ||
                InvalidationExpectedStatusesEnum.OODEP_BEFORE_RELEASE.getOfficeRoleTypeStatuses().getStatuses().contains(processingStatus.getItemCode())) {
            assertTrue(violations.isEmpty());
        } else if(Objects.nonNull(existingInvalidMessageType)) {
            assertEquals(1, invalidMessageOverviewList.size());
            assertEquals(existingInvalidMessageType, invalidMessageOverviewList.get(0).getMessageType());
            IE014ViolationAssertions(violations);
        } else if(!isManuallyInserted && ProcessingStatusTypeEnum.OODEP_UNDER_ENQUIRY_PROCEDURE.equals(processingStatus)) {
            assertTrue(invalidMessageOverviewList.isEmpty());
            IE014ViolationAssertions(violations);
        } else if(!isManuallyInserted && !isElectronicallySubmittedEnabled) {
            verify(dossierDataService, times(0)).getMessagesOverviewByMrnAndMessageTypes(any(), any());
            IE014ViolationAssertions(violations);
        }

    }

    @Test
    @SneakyThrows
    void test_IE018CustomValidation() {

        String cd018cMessage = FileHelpers.xmlToString(MessageTypes.CD_018_C.value() + "-GrossMassZero.xml");

        CD018CType cc018CType = XmlConverter.buildMessageFromXML(cd018cMessage, CD018CType.class);

        cd018cMessage = XmlConverter.marshal(cc018CType, NamespacesEnum.NTA.getValue(), MessageTypes.CD_018_C.value());

        Message message = Message.builder()
                                 .messageType(MessageTypes.CD_018_C.value())
                                 .destination("NTA.GR")
                                 .payload(cd018cMessage)
                                 .build();

        List<Movement> movements = new ArrayList<>();

        List<Violation> violations;

        message.setManualInserted(true);
        when(transitionPeriodService.isNowNCTSTransitionPeriod()).thenReturn(true);

        violations = ddntaMessageCustomValidationService.performCustomValidationPerMessageType(movements, message);
        assertFalse(violations.isEmpty());

    }

    @Test
    @SneakyThrows
    void test_IE044CustomValidation() {

        String cc044cMessage = FileHelpers.xmlToString(MessageTypes.CC_044_C.value() + "-NoGrossMass.xml");

        CC044CType cc044CType = XmlConverter.buildMessageFromXML(cc044cMessage, CC044CType.class);

        cc044cMessage = XmlConverter.marshal(cc044CType, NamespacesEnum.NTA.getValue(), MessageTypes.CC_044_C.value());

        Message message = Message.builder()
                                 .messageType(MessageTypes.CC_044_C.value())
                                 .destination("NTA.GR")
                                 .payload(cc044cMessage)
                                 .build();

        List<Movement> movements = new ArrayList<>();

        List<Violation> violations;

        when(transitionPeriodService.isNowNCTSTransitionPeriod()).thenReturn(true);

        violations = ddntaMessageCustomValidationService.performCustomValidationPerMessageType(movements, message);
        assertTrue(violations.isEmpty());

    }


    private void IE014ViolationAssertions(List<Violation> violations) {
        assertEquals(1, violations.size());
        assertEquals("/CC014C", violations.get(0).getFieldName());
        assertEquals("N/A", violations.get(0).getReason());
        assertNull(violations.get(0).getRejectedValue());
        assertEquals(MessageUtil.ERROR_CODE_FOR_906, violations.get(0).getCode());
    }

    private static Stream<Arguments> namedArguments() {
        return Stream.of(
                Arguments.of(true, true, null, ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED),
                Arguments.of(true, true, null, ProcessingStatusTypeEnum.OODEP_ACCEPTED),
                Arguments.of(true, false, null, ProcessingStatusTypeEnum.OODEP_UNDER_ENQUIRY_PROCEDURE),
                Arguments.of(true, false, MessageTypes.CD_118_C.value(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED),
                Arguments.of(true, false, MessageTypes.CD_168_C.value(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED),
                Arguments.of(true, false, MessageTypes.CD_180_C.value(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED),
                Arguments.of(true, false, MessageTypes.CD_006_C.value(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED),
                Arguments.of(false, true, MessageTypes.CD_118_C.value(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED),
                Arguments.of(false, true, MessageTypes.CD_168_C.value(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED),
                Arguments.of(false, true, MessageTypes.CD_180_C.value(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED),
                Arguments.of(false, true, MessageTypes.CD_006_C.value(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED),
                Arguments.of(false, false, null, ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED)
        );
    }
}
