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

import com.intrasoft.ermis.common.bpmn.core.BpmnEngine;
import com.intrasoft.ermis.common.bpmn.core.CorrelateBpmnMessageCommand;
import com.intrasoft.ermis.common.json.parsing.JsonConverter;
import com.intrasoft.ermis.common.json.parsing.JsonConverterFactory;
import com.intrasoft.ermis.common.logging.ErmisLogger;
import com.intrasoft.ermis.common.xml.parsing.XmlConverter;
import com.intrasoft.ermis.contracts.core.dto.declaration.riskassessment.v2.RiskAssessmentResponseEvent;
import com.intrasoft.ermis.ddntav5152.messages.CC015CType;
import com.intrasoft.ermis.ddntav5152.messages.CC191CType;
import com.intrasoft.ermis.ddntav5152.messages.MessageTypes;
import com.intrasoft.ermis.platform.shared.client.configuration.core.domain.Configuration;
import com.intrasoft.ermis.transit.common.config.services.ErmisConfigurationService;
import com.intrasoft.ermis.transit.common.enums.ProcessingStatusTypeEnum;
import com.intrasoft.ermis.transit.common.exceptions.NotFoundException;
import com.intrasoft.ermis.transit.officeofdeparture.domain.DomainBase;
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.MovementAttribute;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.BpmnFlowMessagesEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.BpmnFlowVariablesEnum;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBException;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.MovementAttributesEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class InitializationServiceImpl implements InitializationService {

    private static final JsonConverter JSON_CONVERTER = JsonConverterFactory.getDefaultJsonConverter();

    private static final String GET_AES_NCTS_COMMUNICATION_ENABLED_RS = "getAESCommunicationEnabledRS";
    
    public static final String GET_SUBMITTED_ENABLED_RS = "getSubmittedEnabledRS";

    private static final String CONTROL_DECISION_NOTIFICATION_ENABLED_RS = "getControlDecisionNotificationEnabledRS";

    private static final String SEND_CONTROL_DECISION_NOTIFICATION_AUTOMATICALLY_ENABLED_RS = "getSendControlDecisionNotificationAutomaticallyEnabledRS";
    
    private static final String DEPEND_ON_RISK_RESPONSE_FOR_NOTIFYING_DECLARANT_ENABLED_RS = "getDependOnRiskResponseForNotifyingDeclarantEnabledRS";
    
    private static final String VALUE_LITERAL = "value";

    private final ErmisLogger log;

    private final ProcessingStatusService processingStatusService;

    private final GenerateMessageService generateMessageService;

    private final DossierDataService dossierDataService;

    private final BpmnEngine bpmnEngine;

    private final ErmisConfigurationService ermisConfigurationService;

    private final TransitAfterAESService transitAfterAESService;

    @Override
    public void registerMessage(String movementId) throws JAXBException {

        processingStatusService.saveProcessingStatus(movementId, ProcessingStatusTypeEnum.OODEP_SUBMITTED.getItemCode());
        generateMessageService.generateIE928(movementId);
    }

    @Override
    public void acceptMessage(String movementId, String mrn, boolean isTransitAfterAES) throws JAXBException {

        processingStatusService.saveProcessingStatus(movementId, ProcessingStatusTypeEnum.OODEP_ACCEPTED.getItemCode());
            generateMessageService.generateIE028(movementId, mrn);
    }

    @Override
    public void rejectMessage(String movementId, String messageId, boolean fetchNonRejectedMessages, boolean negativeIE191, boolean isManuallyRejected, boolean gprTimerExpiration) throws JAXBException {

        processingStatusService.saveProcessingStatus(movementId, ProcessingStatusTypeEnum.OODEP_REJECTED.getItemCode());
         /* Generate IE056 based on IE015 */
        generateMessageService.generateIE056(movementId, messageId, CC015CType.class, fetchNonRejectedMessages, negativeIE191, isManuallyRejected, false);
    }

    @Override
    public boolean isSubmittedStateEnabled() {
        Configuration configuration = ermisConfigurationService.getConfigurationByTemplateName(GET_SUBMITTED_ENABLED_RS);
        String isSubmittedEnabled = configuration.getEvaluationParams().get("submittedEnabled");

        return Boolean.parseBoolean(isSubmittedEnabled);
    }

    @Override
    public void handleIE191Message(String movementId, String messageId) {
        Message message = dossierDataService.getMessageById(messageId);
        List<MessageOverview> messageOverview = dossierDataService.getMessagesOverview(movementId, MessageTypes.CC_013_C.value());
        String processBusinessKey = null;
        if (!messageOverview.isEmpty()) {
            Movement movement = dossierDataService.getMovementById(movementId);
            messageOverview.sort(Comparator.comparing(DomainBase::getCreatedDateTime));
            if (movement == null) {
                throw new NotFoundException("No movement found for movement id".concat(movementId));
            }
            processBusinessKey = movement.getMrn().concat("-").concat(messageOverview.get(messageOverview.size() - 1).getId());
        }
        if (message != null) {
            CC191CType cc191cMessage;
            try {
                cc191cMessage = XmlConverter.buildMessageFromXML(message.getPayload(), CC191CType.class);

                Boolean isIE191MessagePositive = "1".equals(cc191cMessage.getAESResults().getGlobalValidationResponse());
                // IE191 message relates to AES Communication Purpose, this deals with 2 (= Allocation of declaration MRNs...) after (implicit) Initial cross-check of MRNs
                Boolean isAesCommPurposeAlloc = cc191cMessage.getAESResults().getAESCommunicationPurpose() != null && "2".equals(
                        cc191cMessage.getAESResults().getAESCommunicationPurpose());

                final CorrelateBpmnMessageCommand correlateBpmnMessageCommand =
                        createBpmnMessageCommandForIe191PurposeAndResult(movementId, isIE191MessagePositive, isAesCommPurposeAlloc, processBusinessKey);

                bpmnEngine.correlateMessage(correlateBpmnMessageCommand);
            } catch (JAXBException e) {
                log.error("Processing FAILED for handleIE191Message of Message {} and Movement: {}", messageId, movementId);
            }
        }
    }

    @Override
    public boolean isControlDecisionNotificationEnabled() {
        return isControlDecisionNotificationSwitchEnabled();
    }

    @Override
    public boolean shouldSendControlDecisionNotificationAutomatically() {
        return isSendControlDecisionNotificationAutomaticallySwitchEnabled();
    }

    @Override
    public boolean shouldSendNotificationToDeclarant(String movementId) {

        if (isDependOnRiskForNotifyingDeclarantSwitchEnabled()) {

            return shouldNotifyDeclarant(movementId);
        } else {
            // If switch is off, then IE060 should always be sent.
            return true;
        }
    }

    @Override
    public MovementAttribute saveMovementAttribute(String movementId) {
        Configuration configuration = ermisConfigurationService.getConfigurationByTemplateName(GET_AES_NCTS_COMMUNICATION_ENABLED_RS);
        MovementAttribute movementAttribute = null;
        if (configuration != null) {
            movementAttribute = MovementAttribute.builder()
                                                 .movementId(movementId)
                                                 .attrKey(MovementAttributesEnum.AES_COMMUNICATION_ENABLED.getValue())
                                                 .attrValue(configuration.getEvaluationParams().get(MovementAttributesEnum.AES_COMMUNICATION_ENABLED.getValue()))
                                                 .build();
            dossierDataService.saveMovementAttribute(movementAttribute);
        }
        return movementAttribute;
    }

    @Override
    public boolean isDeclarationManuallySubmitted(String messageId) {
        Message cc015cMessage = dossierDataService.getMessageById(messageId);
        return cc015cMessage.isManualInserted();
    }

    private CorrelateBpmnMessageCommand createBpmnMessageCommandForIe191PurposeAndResult(String movementId, Boolean isPositive, Boolean isAlloc,
            String processBusinessKey) {

        Map<String, Object> processVariables = new HashMap<>();
        processVariables.put((Boolean.TRUE.equals(isAlloc) ?
                BpmnFlowVariablesEnum.IS_IE191_MESSAGE_POSITIVE_COMM_PURPOSE_ALLOC.getTypeValue() :
                BpmnFlowVariablesEnum.IS_IE191_MESSAGE_POSITIVE.getTypeValue()), isPositive);
        return (CorrelateBpmnMessageCommand
                .builder()
                .businessKey(processBusinessKey != null ? processBusinessKey : movementId)
                .messageName(Boolean.TRUE.equals(isAlloc) ?
                        BpmnFlowMessagesEnum.IE191_MESSAGE_RECEIVED_COMM_PURPOSE_ALLOC.getTypeValue() :
                        BpmnFlowMessagesEnum.IE191_MESSAGE_RECEIVED.getTypeValue())
                .values(processVariables)
                .build()
        );
    }

    /**
     * Method holding switch logic to enable/disable Control Decision Notification (IE060).
     *
     * @return TRUE/FALSE based on found configuration template.
     */
    private boolean isControlDecisionNotificationSwitchEnabled() {

        Configuration configuration = ermisConfigurationService.getConfigurationByTemplateName(CONTROL_DECISION_NOTIFICATION_ENABLED_RS);
        return Boolean.parseBoolean(configuration.getEvaluationParams().get(VALUE_LITERAL));
    }

    /**
     * Method holding switch logic to enable/disable sending Control Decision Notification automatically (IE060).
     *
     * @return TRUE/FALSE based on found configuration template.
     */
    private boolean isSendControlDecisionNotificationAutomaticallySwitchEnabled() {

        Configuration configuration = ermisConfigurationService.getConfigurationByTemplateName(SEND_CONTROL_DECISION_NOTIFICATION_AUTOMATICALLY_ENABLED_RS);
        return Boolean.parseBoolean(configuration.getEvaluationParams().get(VALUE_LITERAL));
    }

    /**
     * Method holding switch logic to enable/disable notification suppression.
     *
     * @return TRUE/FALSE based on found configuration template.
     */
    private boolean isDependOnRiskForNotifyingDeclarantSwitchEnabled() {

        Configuration configuration = ermisConfigurationService.getConfigurationByTemplateName(DEPEND_ON_RISK_RESPONSE_FOR_NOTIFYING_DECLARANT_ENABLED_RS);
        return Boolean.parseBoolean(configuration.getEvaluationParams().get(VALUE_LITERAL));
    }

    /**
     * Method holding decision logic to notify Declarant (IE060)
     * <br/> based on latest response data from Risk Assessment.
     *
     * @param movementId ID of movement to retrieve risk assessment response of.
     * @return TRUE/FALSE based on data found within risk assessment response.
     */
    private boolean shouldNotifyDeclarant(String movementId) {

        List<MessageOverview> riskAssessmentResponseMessageOverviewList =
                dossierDataService.getMessagesOverview(movementId, RiskAssessmentResponseEvent.class.getSimpleName());

        Comparator<MessageOverview> messageOverviewComparator = Comparator.comparing(DomainBase::getCreatedDateTime);

        if (!riskAssessmentResponseMessageOverviewList.isEmpty()) {
            // Sort and reverse riskAssessmentResponseMessageOverviewList so the latest is on index[0]:
            riskAssessmentResponseMessageOverviewList.sort(messageOverviewComparator);
            Collections.reverse(riskAssessmentResponseMessageOverviewList);

            try {

                Message riskAssessmentResponseMessage = dossierDataService.getMessageById(riskAssessmentResponseMessageOverviewList.get(0).getId());

                RiskAssessmentResponseEvent riskAssessmentResponseEvent =
                        JSON_CONVERTER.convertStringToObject(riskAssessmentResponseMessage.getPayload(), RiskAssessmentResponseEvent.class);
                // If at least one (1) control instruction has involvesNotifyingDeclarant == FALSE, then suppress notification:
                return riskAssessmentResponseEvent.getRiskAssessmentResults()
                                                  .stream()
                                                  .takeWhile(riskAssessmentResult ->
                                                          riskAssessmentResult.getControlInstructions()
                                                                              .stream()
                                                                              .anyMatch(controlInstruction ->
                                                                                      Boolean.FALSE.equals(controlInstruction.isInvolvesNotifyingDeclarant())))
                                                  .findAny()
                                                  .isEmpty();
            } catch (Exception e) {
                // Default TRUE if an Exception was thrown for provided movement ID (non-interrupting exception handling):
                log.warn("Failed to retrieve Risk Assessment Response data for movementId: " + movementId + ". Defaulting to TRUE.");
                return true;
            }
        } else {
            // Default TRUE if no Risk Assessment Response Event Messages were found for provided movement ID:
            log.warn("shouldNotifyDeclarant(): No Risk Assessment Response Event Messages found. Defaulting to TRUE.");
            return true;
        }
    }
}

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

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

import com.intrasoft.ermis.common.bpmn.core.BpmnEngine;
import com.intrasoft.ermis.common.json.parsing.JsonConverter;
import com.intrasoft.ermis.common.json.parsing.JsonConverterFactory;
import com.intrasoft.ermis.contracts.core.dto.declaration.riskassessment.ControlInstruction;
import com.intrasoft.ermis.contracts.core.dto.declaration.riskassessment.v2.RiskAssessmentResponseEvent;
import com.intrasoft.ermis.contracts.core.dto.declaration.riskassessment.v2.RiskAssessmentResult;
import com.intrasoft.ermis.platform.shared.client.configuration.core.domain.Configuration;
import com.intrasoft.ermis.transit.common.config.services.ErmisConfigurationService;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Message;
import com.intrasoft.ermis.transit.officeofdeparture.domain.MessageOverview;
import com.intrasoft.ermis.transit.officeofdeparture.service.DossierDataService;
import com.intrasoft.ermis.transit.officeofdeparture.service.GenerateMessageService;
import com.intrasoft.ermis.transit.officeofdeparture.service.InitializationServiceImpl;
import com.intrasoft.ermis.transit.officeofdeparture.service.ProcessingStatusService;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

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.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;


@ExtendWith(MockitoExtension.class)
class InitializationServiceUT {

    private static final JsonConverter JSON_CONVERTER = JsonConverterFactory.getDefaultJsonConverter();

    private static final String CONTROL_DECISION_NOTIFICATION_ENABLED_RS = "getControlDecisionNotificationEnabledRS";

    private static final String SEND_CONTROL_DECISION_NOTIFICATION_AUTOMATICALLY_ENABLED_RS = "getSendControlDecisionNotificationAutomaticallyEnabledRS";

    private static final String DEPEND_ON_RISK_RESPONSE_FOR_NOTIFYING_DECLARANT_ENABLED_RS = "getDependOnRiskResponseForNotifyingDeclarantEnabledRS";

    private static final String MOVEMENT_ID = UUID.randomUUID().toString();

    private static final String LATEST_MESSAGE_ID = "MESSAGE_ID_0";

    private static final int NUMBER_OF_MESSAGE_OVERVIEWS = 3;

    private static final int NUMBER_OF_RISK_ASSESSMENT_RESULTS = 2;

    private static final int NUMBER_OF_CONTROL_INSTRUCTIONS = 4;

    @Mock
    private ProcessingStatusService processingStatusService;

    @Mock
    private GenerateMessageService generateMessageService;

    @Mock
    private BpmnEngine bpmnEngine;

    @Mock
    private DossierDataService dossierDataService;

    @Mock
    private ErmisConfigurationService ermisConfigurationService;

    @InjectMocks
    private InitializationServiceImpl initializationService;

    @ParameterizedTest
    @DisplayName("Test - Is Control Decision Notification Enabled")
    @ValueSource(strings = {"true", "false", "invalidParameter"})
    void testIsControlDecisionNotificationEnabled(String valueParameter) {
        // Stubs:
        when(ermisConfigurationService.getConfigurationByTemplateName(CONTROL_DECISION_NOTIFICATION_ENABLED_RS))
                .thenReturn(buildConfiguration(CONTROL_DECISION_NOTIFICATION_ENABLED_RS, valueParameter));
        // Call tested service method:
        boolean returnedValue = initializationService.isControlDecisionNotificationEnabled();
        // Verifications & Assertions:
        verify(ermisConfigurationService, times(1)).getConfigurationByTemplateName(CONTROL_DECISION_NOTIFICATION_ENABLED_RS);
        if ("invalidParameter".equals(valueParameter)) {
            assertFalse(returnedValue);
        } else {
            assertEquals(Boolean.parseBoolean(valueParameter), returnedValue);
        }
    }

    @ParameterizedTest
    @DisplayName("Test - Should Send Control Decision Notification Automatically")
    @ValueSource(strings = {"true", "false", "invalidParameter"})
    void testShouldSendControlDecisionNotificationAutomatically(String valueParameter) {
        // Stubs:
        when(ermisConfigurationService.getConfigurationByTemplateName(SEND_CONTROL_DECISION_NOTIFICATION_AUTOMATICALLY_ENABLED_RS))
                .thenReturn(buildConfiguration(SEND_CONTROL_DECISION_NOTIFICATION_AUTOMATICALLY_ENABLED_RS, valueParameter));
        // Call tested service method:
        boolean returnedValue = initializationService.shouldSendControlDecisionNotificationAutomatically();
        // Verifications & Assertions:
        verify(ermisConfigurationService, times(1))
                .getConfigurationByTemplateName(SEND_CONTROL_DECISION_NOTIFICATION_AUTOMATICALLY_ENABLED_RS);
        if ("invalidParameter".equals(valueParameter)) {
            assertFalse(returnedValue);
        } else {
            assertEquals(Boolean.parseBoolean(valueParameter), returnedValue);
        }
    }

    @ParameterizedTest
    @DisplayName("Test - Should Send Notification To Declarant")
    @CsvSource({"true,true", "true,false", "false,true", "false,false",})
    void testShouldSendNotificationToDeclarant(boolean switchEnabled, boolean suppressNotification) {
        // Stubs:
        when(ermisConfigurationService.getConfigurationByTemplateName(DEPEND_ON_RISK_RESPONSE_FOR_NOTIFYING_DECLARANT_ENABLED_RS))
                .thenReturn(buildConfiguration(DEPEND_ON_RISK_RESPONSE_FOR_NOTIFYING_DECLARANT_ENABLED_RS, String.valueOf(switchEnabled)));
        if (switchEnabled) {
            when(dossierDataService.getMessagesOverview(MOVEMENT_ID, RiskAssessmentResponseEvent.class.getSimpleName()))
                    .thenReturn(buildMessageOverviewList());
            when(dossierDataService.getMessageById(LATEST_MESSAGE_ID))
                    .thenReturn(buildMessage(suppressNotification));
        }
        // Call tested service method:
        boolean returnedValue = initializationService.shouldSendNotificationToDeclarant(MOVEMENT_ID);
        // Verifications & Assertions:
        verify(ermisConfigurationService, times(1)).getConfigurationByTemplateName(DEPEND_ON_RISK_RESPONSE_FOR_NOTIFYING_DECLARANT_ENABLED_RS);
        if (switchEnabled) {
            // If switch is enabled, further REST calls should be made:
            verify(dossierDataService, times(1)).getMessagesOverview(MOVEMENT_ID, RiskAssessmentResponseEvent.class.getSimpleName());
            verify(dossierDataService, times(1)).getMessageById(LATEST_MESSAGE_ID);
            // If at least one involvesNotifyingDeclarant within control instructions is found FALSE, suppress notification:
            if (suppressNotification) {
                assertFalse(returnedValue);
            } else {
                assertTrue(returnedValue);
            }
        } else {
            // If switch is disabled, no further REST calls should be made and should notify declarant:
            verify(dossierDataService, times(0)).getMessagesOverview(MOVEMENT_ID, RiskAssessmentResponseEvent.class.getSimpleName());
            verify(dossierDataService, times(0)).getMessageById(LATEST_MESSAGE_ID);
            assertTrue(returnedValue);
        }

    }

    private Configuration buildConfiguration(String ruleTemplate, String value) {

        return new Configuration(
                ruleTemplate,
                "ruleCode",
                new HashMap<>(),
                Map.of("value", value),
                "errorCode",
                LocalDateTime.now(ZoneOffset.UTC),
                LocalDateTime.now(ZoneOffset.UTC).minusDays(60),
                LocalDateTime.now(ZoneOffset.UTC).plusDays(60));
    }

    private List<MessageOverview> buildMessageOverviewList() {

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

        for (int i = 0; i < NUMBER_OF_MESSAGE_OVERVIEWS; i++) {

            MessageOverview messageOverview = MessageOverview.builder()
                                                             .id("MESSAGE_ID_" + i)
                                                             .messageType(RiskAssessmentResponseEvent.class.getSimpleName())
                                                             .createdDateTime(LocalDateTime.now(ZoneOffset.UTC).minusDays(i))
                                                             .build();
            messageOverviewList.add(messageOverview);
        }

        return messageOverviewList;
    }

    private Message buildMessage(boolean suppressNotification) {

        Message message = new Message();
        RiskAssessmentResponseEvent riskAssessmentResponseEvent = new RiskAssessmentResponseEvent();
        riskAssessmentResponseEvent.setRiskAssessmentResults(new ArrayList<>());
        riskAssessmentResponseEvent.getRiskAssessmentResults().addAll(buildRiskAssessmentResultList(suppressNotification));

        message.setMessageIdentification(LATEST_MESSAGE_ID);
        message.setMessageType(RiskAssessmentResponseEvent.class.getSimpleName());
        message.setPayload(JSON_CONVERTER.convertToJsonString(riskAssessmentResponseEvent));

        return message;
    }

    private List<RiskAssessmentResult> buildRiskAssessmentResultList(boolean suppressNotification) {

        List<RiskAssessmentResult> riskAssessmentResultList = new ArrayList<>();

        for (int i = 0; i < NUMBER_OF_RISK_ASSESSMENT_RESULTS; i++) {

            RiskAssessmentResult riskAssessmentResult = new RiskAssessmentResult();

            for (int j = 0; j < NUMBER_OF_CONTROL_INSTRUCTIONS; j++) {

                ControlInstruction controlInstruction = new ControlInstruction();
                controlInstruction.setInvolvesNotifyingDeclarant(true);

                riskAssessmentResult.getControlInstructions().add(controlInstruction);
            }

            riskAssessmentResultList.add(riskAssessmentResult);
        }

        if (suppressNotification) {
            riskAssessmentResultList.get(0).getControlInstructions().get(0).setInvolvesNotifyingDeclarant(false);
        }

        return riskAssessmentResultList;
    }
}
