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

import com.intrasoft.ermis.common.bpmn.core.BpmnEngine;
import com.intrasoft.ermis.common.bpmn.core.CorrelateBpmnMessageCommand;
import com.intrasoft.ermis.common.drools.core.RuleService;
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.contracts.core.dto.declaration.common.CustomsData;
import com.intrasoft.ermis.contracts.core.dto.declaration.common.CustomsDataPayloadContentTypeEnum;
import com.intrasoft.ermis.contracts.core.dto.declaration.validation.v2.ValidationResultDTO;
import com.intrasoft.ermis.contracts.core.dto.declaration.validation.v2.events.ExternalValidationRequestEvent;
import com.intrasoft.ermis.contracts.core.dto.declaration.validation.v2.events.ExternalValidationResponseEvent;
import com.intrasoft.ermis.platform.contracts.dossier.dtos.MessageDTO;
import com.intrasoft.ermis.platform.shared.client.configuration.core.domain.Configuration;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.ReferenceDataAdapter;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.CodeListDomain;
import com.intrasoft.ermis.transit.common.config.services.ErmisConfigurationService;
import com.intrasoft.ermis.transit.common.config.services.GetCountryCodeService;
import com.intrasoft.ermis.transit.common.config.timer.TimerDelayExecutor;
import com.intrasoft.ermis.transit.common.enums.DirectionEnum;
import com.intrasoft.ermis.transit.common.enums.MessageDomainTypeEnum;
import com.intrasoft.ermis.transit.common.enums.ValidationStrategyEnum;
import com.intrasoft.ermis.transit.common.exceptions.BaseException;
import com.intrasoft.ermis.transit.common.mappers.MessageTypeMapper;
import com.intrasoft.ermis.transit.common.transition.period.TransitionPeriodService;
import com.intrasoft.ermis.transit.contracts.core.domain.TimerInfo;
import com.intrasoft.ermis.transit.contracts.core.enums.RegimeEnum;
import com.intrasoft.ermis.transit.validation.Declaration;
import com.intrasoft.ermis.transit.validation.Movement;
import com.intrasoft.ermis.transit.validation.Request;
import com.intrasoft.ermis.transit.validation.Response;
import com.intrasoft.ermis.transit.validation.ValidationResult;
import com.intrasoft.ermis.transit.validation.builders.ValidationMessageWrapperBuilder;
import com.intrasoft.ermis.transit.validation.configuration.ValidationDDNTASpecificationConfigurationProperties;
import com.intrasoft.ermis.transit.validation.drools.domain.ExternalSystemValidationInstruction;
import com.intrasoft.ermis.transit.validation.drools.rules.ValidationMessageWrapper;
import com.intrasoft.ermis.transit.validation.enums.BRSessionEnum;
import com.intrasoft.ermis.transit.validation.enums.BpmnFlowVariablesEnum;
import com.intrasoft.ermis.transit.validation.enums.BpmnMessageNamesEnum;
import com.intrasoft.ermis.transit.validation.enums.ExternalValidationRequestStatusEnum;
import com.intrasoft.ermis.transit.validation.port.outbound.ExternalSystemValidationRequestProducer;
import com.intrasoft.ermis.transit.validation.port.outbound.mapper.SharedKernelValidationResultMapper;
import com.intrasoft.ermis.transit.validation.port.outbound.mapper.ValidationResultDTOMapper;
import com.intrasoft.proddev.referencedata.shared.enums.CodelistKeyEnum;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ExternalSystemValidationServiceImpl implements ExternalSystemValidationService {

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

    // Configuration Rules:
    private static final String TRANSIT_EXTERNAL_SYSTEM_VALIDATION_SWITCH_RS = "getTransitExternalSystemValidationSwitchRS";

    private static final String TRANSIT_EXTERNAL_SYSTEM_VALIDATION_RESPONSE_TIMER_RS = "getTransitExternalSystemValidationResponseDelayTimerRS";

    private static final String BUNDLE_DECLARATION_IN_EXTERNAL_SYSTEM_VALIDATION_RS = "getBundleDeclarationInExternalSystemValidationRS";

    private static final String APPLICABLE_MESSAGE_TYPES_FOR_DECLARATION_BUNDLING_RS = "getApplicableMessageTypesForDeclarationBundlingRS";

    // Evaluation parameter literals:
    private static final String SWITCH_VALUE_LITERAL = "switchValue";

    private static final String ENABLED_LITERAL = "enabled";

    private static final String APPLICABLE_MESSAGE_TYPES_LITERAL = "applicableMessageTypes";

    // Autowired beans:
    private final ErmisLogger log;

    private final ValidationDDNTASpecificationConfigurationProperties validationDDNTASpecificationConfigurationProperties;

    private final TimerDelayExecutor timerDelayExecutor;

    private final ErmisConfigurationService ermisConfigurationService;

    private final ReferenceDataAdapter referenceDataAdapter;

    private final TransitionPeriodService transitionPeriodService;

    private final DossierDataService dossierDataService;

    private final GetCountryCodeService getCountryCodeService;

    private final RuleService ruleService;

    private final ValidationResultDTOMapper validationResultDTOMapper;

    private final SharedKernelValidationResultMapper sharedKernelValidationResultMapper;

    private final BpmnEngine bpmnEngine;

    private final ExternalSystemValidationRequestProducer externalSystemValidationRequestProducer;

    private final ValidationMessageWrapperBuilder validationMessageWrapperBuilder;

    @Override
    public boolean isExternalSystemValidationEnabled() {

        Configuration configuration = ermisConfigurationService.getConfigurationByTemplateName(TRANSIT_EXTERNAL_SYSTEM_VALIDATION_SWITCH_RS);

        boolean isEnabled = "enabled".equals(configuration.getEvaluationParams().get(SWITCH_VALUE_LITERAL).toLowerCase(Locale.ROOT));
        // Log message to correct invalid value detected in Configuration, while method defaults to false:
        if (!("enabled".equals(configuration.getEvaluationParams().get(SWITCH_VALUE_LITERAL).toLowerCase(Locale.ROOT)))
                && !("disabled".equals(configuration.getEvaluationParams().get(SWITCH_VALUE_LITERAL).toLowerCase(Locale.ROOT)))) {

            log.warn("Switch parameter in BR20026 contains invalid value: {}. "
                            + "Please correct with \"enabled\" or \"disabled\". "
                            + "Defaulting to \"disabled\".",
                    configuration.getEvaluationParams().get(SWITCH_VALUE_LITERAL));
        }

        return isEnabled;
    }

    @Override
    public void initiateExternalSystemValidationProcess(String processBusinessKey, Map<String, Object> values) {

        log.info("ENTER initiateExternalSystemValidationProcess() with processBusinessKey: {}", processBusinessKey);
        CorrelateBpmnMessageCommand correlateBpmnMessageCommand = CorrelateBpmnMessageCommand
                .builder()
                .messageName(BpmnMessageNamesEnum.REQUEST_EXTERNAL_SYSTEM_VALIDATION_MESSAGE.getValue())
                .businessKey(processBusinessKey)
                .values(values)
                .build();
        bpmnEngine.correlateMessage(correlateBpmnMessageCommand);
        log.info("EXIT initiateExternalSystemValidationProcess() with processBusinessKey: {}", processBusinessKey);
    }

    @Override
    public List<ExternalSystemValidationInstruction> determineExternalSystems(String messageId) {

        MessageDTO messageDTO = dossierDataService.getMessageByMessageId(messageId);

        ValidationMessageWrapper messageWrapper = validationMessageWrapperBuilder.buildValidationMessageWrapper(messageDTO,
                transitionPeriodService.getNCTSTPAndL3EndDates());

        List<Object> facts = new ArrayList<>();
        facts.add(messageWrapper);

        Map<String, Object> globals = new HashMap<>();
        globals.put("externalSystemInstructionList", new ArrayList<ExternalSystemValidationInstruction>());

        ruleService.executeStateless(BRSessionEnum.EXTERNAL_SYSTEM_INSTRUCTION_IDENTIFICATION_RS.getSessionName(), globals, facts);

        @SuppressWarnings("unchecked")
        List<ExternalSystemValidationInstruction> externalSystemList = (List<ExternalSystemValidationInstruction>) globals.get("externalSystemInstructionList");

        return externalSystemList;
    }

    @Override
    public boolean externalSystemValidationInstructionsExist(List<ExternalSystemValidationInstruction> externalSystemValidationInstructionList) {

        if (externalSystemValidationInstructionList != null) {
            return !externalSystemValidationInstructionList.isEmpty();
        } else {
            throw new BaseException("externalSystemValidationInstructionsExist(): ERROR - externalSystemValidationInstructionList was NULL!");
        }
    }

    @Override
    public TimerInfo getExternalSystemResponseTimerDelayConfiguration() {
        return timerDelayExecutor.getTimerDelayConfig(LocalDateTime.now(ZoneOffset.UTC), LocalDateTime.now(ZoneOffset.UTC),
                                         TRANSIT_EXTERNAL_SYSTEM_VALIDATION_RESPONSE_TIMER_RS, false)
                                 .orElseThrow(() -> new BaseException("Failed to setup External System Response timer!"));
    }

    @Override
    public void sendExternalValidationRequest(String requestId, String externalSystem, String messageId) {

        ExternalValidationRequestEvent externalValidationRequestEvent = buildExternalValidationRequestEvent(requestId, externalSystem, messageId);
        String requestMessageId = persistExternalValidationRequestEvent(externalValidationRequestEvent);
        persistRequest(requestId, requestMessageId, messageId);
        externalSystemValidationRequestProducer.publishExternalSystemValidationRequest(externalValidationRequestEvent);
    }

    @Override
    public void handleExternalSystemValidationResponse(String messageId, String externalSystem, String requestId, boolean isRejecting) {

        try {

            Map<String, Object> values = new HashMap<>();
            values.put(BpmnFlowVariablesEnum.EXTERNAL_SYSTEM_VALIDATION_REJECTION_VALUE_CHILD_LOCAL.getValue(), isRejecting);

            CorrelateBpmnMessageCommand correlateBpmnMessageCommand = CorrelateBpmnMessageCommand.builder()
                                                                                                 .messageName(
                                                                                                         BpmnMessageNamesEnum.EXTERNAL_VALIDATION_RESPONSE_RECEIVED_MESSAGE.getValue())
                                                                                                 .processInstanceId(requestId)
                                                                                                 .values(values)
                                                                                                 .build();

            bpmnEngine.correlateMessage(correlateBpmnMessageCommand);

            log.info("Successfully correlated {} External System Validation Results for Message with ID: {}", externalSystem, messageId);
        } catch (Exception e) {

            throw new BaseException("ERROR: Could not correlate BPMN Command for Message with ID: " + messageId, e);
        }
    }

    @Override
    public List<ValidationResult> persistExternalSystemValidationResults(List<ValidationResult> validationResultList) {

        if (validationResultList != null) {
            return validationResultDTOMapper.toDomain(dossierDataService.saveValidationResults(validationResultDTOMapper.toDTO(validationResultList)));
        } else {
            return new ArrayList<>();
        }
    }

    @Override
    public void handleRequestTimeout(String messageId) {
        // Find all Requests for the Message undergoing validation that are still PENDING:
        List<Request> requestList = dossierDataService.getRequestsByCriteria(null, messageId, ExternalValidationRequestStatusEnum.PENDING.name());
        // For each Request found, retrieve the ExternalValidationRequestEvent Messages and save their Destination (External System):
        List<String> externalSystemList = new ArrayList<>();
        requestList.forEach(request -> externalSystemList.add(dossierDataService.getMessageByMessageId(request.getRequestMessageId()).getDestination()));
        // Place External Systems as a JSON Array in ValidationResult's pointer field:
        String jsonArrayPointer = JSON_CONVERTER.convertToJsonString(externalSystemList);
        // Update each Request's status to 'EXPIRED':
        requestList.forEach(request -> dossierDataService.updateRequestStatus(request.getId(), ExternalValidationRequestStatusEnum.EXPIRED.name()));
        // Persist informative Validation Result that holds the External Systems for which the timer expired:
        List<ValidationResult> validationResultList = new ArrayList<>();
        ValidationResult informativeValidationResult = ValidationResult.builder()
                                                                       .messageId(messageId)
                                                                       .createdDateTime(LocalDateTime.now(ZoneOffset.UTC))
                                                                       .rejecting(false)
                                                                       .ruleId("SLA_TIMER_EXPIRATION")
                                                                       .errorCode("INTERNAL")
                                                                       .pointer(jsonArrayPointer)
                                                                       .build();

        validationResultList.add(informativeValidationResult);
        dossierDataService.saveValidationResults(validationResultDTOMapper.toDTO(validationResultList));
    }

    @Override
    public void propagateExternalSystemValidationResults(String messageId, List<Boolean> validationRejectionList) {

        boolean isRejecting = validationRejectionList.stream().anyMatch(Boolean.TRUE::equals);

        CorrelateBpmnMessageCommand messageCommand = CorrelateBpmnMessageCommand.builder()
                                                                                .businessKey(messageId)
                                                                                .messageName(convertStrategyToMessageName(
                                                                                        ValidationStrategyEnum.EXTERNAL_SYSTEM_VALIDATION_STRATEGY.getStrategy()))
                                                                                .value(BpmnFlowVariablesEnum.IS_REJECTING.getValue(), isRejecting)
                                                                                .build();

        bpmnEngine.correlateMessage(messageCommand);
    }

    @Override
    public List<ValidationResult> mapToTransitValidationResultList(List<ValidationResultDTO> externalValidationResultList, String businessKey) {

        List<ValidationResult> validationResultList = new ArrayList<>();

        externalValidationResultList.forEach(validationResultDTO -> {

            boolean isRejecting =
                    referenceDataAdapter.verifyCodeListItem(CodelistKeyEnum.VALIDATION_RESULT_TYPES.getKey(), validationResultDTO.getValidationResultType(),
                            LocalDate.now(ZoneOffset.UTC), null, CodeListDomain.ERMIS)
                            && referenceDataAdapter.verifyCodeListItem(CodelistKeyEnum.REJECTING_VALIDATION_RESULTS.getKey(),
                            validationResultDTO.getValidationResultType(), LocalDate.now(ZoneOffset.UTC), null, CodeListDomain.ERMIS);

            ValidationResult validationResult = sharedKernelValidationResultMapper.toDomain(validationResultDTO);
            validationResult.setRejecting(isRejecting);
            validationResult.setMessageId(businessKey);

            validationResultList.add(validationResult);
        });

        return validationResultList;
    }

    @Override
    public boolean isResponseExpected(String requestId) {

        try {

            return ExternalValidationRequestStatusEnum.PENDING.name().equals(dossierDataService.getRequestById(requestId).getStatus());
        } catch (Exception e) {

            return false;
        }
    }

    @Override
    public String persistExternalSystemValidationResponse(ExternalValidationResponseEvent externalValidationResponseEvent) {

        return persistExternalValidationResponseEvent(externalValidationResponseEvent);

    }

    @Override
    public void persistResponse(String responseId, String requestId, String responseMessageId, boolean finalResponse) {

        Response response = Response.builder()
                                    .id(responseId)
                                    .responseMessageId(responseMessageId)
                                    .requestId(requestId)
                                    .finalResponse(finalResponse)
                                    .createdDateTime(LocalDateTime.now(ZoneOffset.UTC))
                                    .build();

        dossierDataService.saveResponse(response);
    }

    @Override
    public void updateRequestStatus(String id, String status) {

        dossierDataService.updateRequestStatus(id, status);
    }

    private ExternalValidationRequestEvent buildExternalValidationRequestEvent(String requestId, String externalSystem, String messageId) {

        try {

            MessageDTO messageDTO = dossierDataService.getMessageByMessageId(messageId);

            ExternalValidationRequestEvent externalValidationRequestEvent = new ExternalValidationRequestEvent();

            externalValidationRequestEvent.setRequestId(requestId);
            externalValidationRequestEvent.setExternalSystem(externalSystem);
            // In Validation, businessKey == messageId:
            externalValidationRequestEvent.setBusinessKey(messageId);
            externalValidationRequestEvent.setRegime(RegimeEnum.TRANSIT.getValue());
            externalValidationRequestEvent.setReferenceDate(messageDTO.getCreatedDateTime());
            // Populate Validation Data (Customs Data):
            externalValidationRequestEvent.setValidationData(buildCustomsDataSet(messageDTO));

            return externalValidationRequestEvent;
        } catch (Exception e) {

            throw new BaseException("ERROR building ExternalValidationRequestEvent for Message with ID: " + messageId, e);
        }
    }

    private Set<CustomsData> buildCustomsDataSet(MessageDTO messageDTO) {

        Set<CustomsData> customsDataSet = new HashSet<>();
        // Default customsData:
        CustomsData customsData = new CustomsData();
        customsData.setSequenceNumber(BigInteger.ONE);
        customsData.setPayloadSpec(validationDDNTASpecificationConfigurationProperties.getDDNTASpecification());
        customsData.setType(messageDTO.getMessageType());
        customsData.setPayloadType(CustomsDataPayloadContentTypeEnum.XML.toString());
        customsData.setPayload(messageDTO.getPayload());
        customsDataSet.add(customsData);
        // Configurable customsData - extension to add Declaration:
        boolean includeDeclaration = shouldIncludeDeclaration();
        // Check applicability and take proper actions:
        if (includeDeclaration) {
            List<String> applicableMessageTypes = getApplicableMessageTypes();
            if (applicableMessageTypes.contains(messageDTO.getMessageType())) {
                // Fetch associated Movement via messageId:
                Movement movement = dossierDataService.findAssociatedMovementViaMessageId(messageDTO.getId());
                // Retrieve Declaration via movementId:
                Declaration declaration = dossierDataService.findLatestDeclarationViaMovementId(movement.getId());
                // Set appropriate data:
                CustomsData declarationCustomsData = new CustomsData();
                declarationCustomsData.setSequenceNumber(BigInteger.TWO);
                declarationCustomsData.setPayloadSpec(validationDDNTASpecificationConfigurationProperties.getDDNTASpecification());
                declarationCustomsData.setType(declaration.getPayloadType());
                declarationCustomsData.setPayloadType(CustomsDataPayloadContentTypeEnum.XML.toString());
                declarationCustomsData.setPayload(declaration.getPayload());
                customsDataSet.add(declarationCustomsData);
            }
        }
        return customsDataSet;
    }

    private boolean shouldIncludeDeclaration() {
        // Retrieve configuration:
        Configuration configuration = ermisConfigurationService.getConfigurationByTemplateName(BUNDLE_DECLARATION_IN_EXTERNAL_SYSTEM_VALIDATION_RS);
        return Boolean.parseBoolean(configuration.getEvaluationParams().get(ENABLED_LITERAL));
    }

    private List<String> getApplicableMessageTypes() {
        // Retrieve configuration:
        Configuration configuration = ermisConfigurationService.getConfigurationByTemplateName(APPLICABLE_MESSAGE_TYPES_FOR_DECLARATION_BUNDLING_RS);
        // Parse the CSV String parameter, upper case it, strip it from whitespaces, split it and return as list:
        return Arrays.stream(configuration.getEvaluationParams().get(APPLICABLE_MESSAGE_TYPES_LITERAL)
                                          .toUpperCase(Locale.ROOT)
                                          .replaceAll("\\s", "")
                                          .split(","))
                     .toList();
    }

    private String persistExternalValidationRequestEvent(ExternalValidationRequestEvent externalValidationRequestEvent) {

        MessageDTO externalValidationRequestEventMessage = new MessageDTO();

        externalValidationRequestEventMessage.setMessageType(ExternalValidationRequestEvent.class.getSimpleName());
        externalValidationRequestEventMessage.setPayload(JSON_CONVERTER.convertToJsonString(externalValidationRequestEvent));
        externalValidationRequestEventMessage.setSource(MessageTypeMapper.NTA + getCountryCodeService.getCountryCode());
        externalValidationRequestEventMessage.setDestination(externalValidationRequestEvent.getExternalSystem());
        externalValidationRequestEventMessage.setDomain(MessageDomainTypeEnum.ERMIS.name());
        // ID of Message undergoing validation:
        externalValidationRequestEventMessage.setMessageIdentification(externalValidationRequestEvent.getBusinessKey());
        externalValidationRequestEventMessage.setDirection(DirectionEnum.SENT.getDirection());

        return dossierDataService.saveMessage(externalValidationRequestEventMessage).getId();
    }

    private void persistRequest(String requestId, String requestMessageId, String referencedMessageId) {

        Request request = Request.builder()
                                 .id(requestId)
                                 .requestMessageId(requestMessageId)
                                 .referencedMessageId(referencedMessageId)
                                 .status(ExternalValidationRequestStatusEnum.PENDING.name())
                                 .createdDateTime(LocalDateTime.now(ZoneOffset.UTC))
                                 .build();

        dossierDataService.saveRequest(request);
    }

    private String persistExternalValidationResponseEvent(ExternalValidationResponseEvent externalValidationResponseEvent) {

        MessageDTO externalValidationResponseEventMessage = new MessageDTO();

        externalValidationResponseEventMessage.setMessageType(ExternalValidationResponseEvent.class.getSimpleName());
        externalValidationResponseEventMessage.setPayload(JSON_CONVERTER.convertToJsonString(externalValidationResponseEvent));
        externalValidationResponseEventMessage.setSource(externalValidationResponseEvent.getExternalSystem());
        externalValidationResponseEventMessage.setDestination(MessageTypeMapper.NTA + getCountryCodeService.getCountryCode());
        externalValidationResponseEventMessage.setDomain(MessageDomainTypeEnum.ERMIS.name());
        // ID of Message undergoing validation:
        externalValidationResponseEventMessage.setMessageIdentification(externalValidationResponseEvent.getBusinessKey());
        externalValidationResponseEventMessage.setDirection(DirectionEnum.RECEIVED.getDirection());

        return dossierDataService.saveMessage(externalValidationResponseEventMessage).getId();
    }

    private String convertStrategyToMessageName(String strategy) {

        strategy = StringUtils.uncapitalize(strategy);

        return strategy.replace("ValidationStrategy", "ValidationCompletedMessage");
    }
}

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

import static com.intrasoft.ermis.transit.validation.util.DataGenerators.createIE015Message;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
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.bpmn.core.BpmnEngine;
import com.intrasoft.ermis.common.bpmn.core.CorrelateBpmnMessageCommand;
import com.intrasoft.ermis.common.drools.core.RuleService;
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.enums.NamespacesEnum;
import com.intrasoft.ermis.common.xml.parsing.XmlConverter;
import com.intrasoft.ermis.contracts.core.dto.declaration.common.CustomsData;
import com.intrasoft.ermis.contracts.core.dto.declaration.common.CustomsDataPayloadContentTypeEnum;
import com.intrasoft.ermis.contracts.core.dto.declaration.validation.v2.RuleDTO;
import com.intrasoft.ermis.contracts.core.dto.declaration.validation.v2.ValidationInformationDTO;
import com.intrasoft.ermis.contracts.core.dto.declaration.validation.v2.ValidationResultDTO;
import com.intrasoft.ermis.contracts.core.dto.declaration.validation.v2.events.ExternalValidationRequestEvent;
import com.intrasoft.ermis.contracts.core.dto.declaration.validation.v2.events.ExternalValidationResponseEvent;
import com.intrasoft.ermis.ddntav5152.messages.CC015CType;
import com.intrasoft.ermis.ddntav5152.messages.MessageTypes;
import com.intrasoft.ermis.platform.shared.client.configuration.core.domain.Configuration;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.ReferenceDataAdapter;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.Attribute;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.CodeListDomain;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.CodeListItem;
import com.intrasoft.ermis.transit.common.config.services.ErmisConfigurationService;
import com.intrasoft.ermis.transit.common.config.services.GetCountryCodeService;
import com.intrasoft.ermis.transit.common.config.timer.TimerDelayExecutor;
import com.intrasoft.ermis.transit.common.enums.DirectionEnum;
import com.intrasoft.ermis.transit.common.enums.MessageDomainTypeEnum;
import com.intrasoft.ermis.transit.common.mappers.MessageTypeMapper;
import com.intrasoft.ermis.transit.common.transition.period.TransitionPeriodService;
import com.intrasoft.ermis.transit.common.util.Pair;
import com.intrasoft.ermis.transit.contracts.core.domain.TimerInfo;
import com.intrasoft.ermis.transit.contracts.core.enums.RegimeEnum;
import com.intrasoft.ermis.transit.contracts.core.enums.TimeUnitEnum;
import com.intrasoft.ermis.platform.contracts.dossier.dtos.MessageDTO;
import com.intrasoft.ermis.transit.validation.Request;
import com.intrasoft.ermis.transit.validation.Response;
import com.intrasoft.ermis.transit.validation.ValidationResult;
import com.intrasoft.ermis.transit.validation.builders.ValidationMessageWrapperBuilder;
import com.intrasoft.ermis.transit.validation.configuration.ValidationDDNTASpecificationConfigurationProperties;
import com.intrasoft.ermis.transit.validation.drools.domain.ExternalSystemValidationInstruction;
import com.intrasoft.ermis.transit.validation.enums.BRSessionEnum;
import com.intrasoft.ermis.transit.validation.enums.BpmnFlowVariablesEnum;
import com.intrasoft.ermis.transit.validation.enums.BpmnMessageNamesEnum;
import com.intrasoft.ermis.transit.validation.enums.ExternalValidationRequestStatusEnum;
import com.intrasoft.ermis.transit.validation.message.MessageHelper;
import com.intrasoft.ermis.transit.validation.port.outbound.ExternalSystemValidationRequestProducer;
import com.intrasoft.ermis.transit.validation.port.outbound.mapper.SharedKernelValidationResultMapper;
import com.intrasoft.ermis.transit.validation.port.outbound.mapper.SharedKernelValidationResultMapperImpl;
import com.intrasoft.ermis.transit.validation.port.outbound.mapper.ValidationResultDTOMapper;
import com.intrasoft.ermis.transit.validation.port.outbound.mapper.ValidationResultDTOMapperImpl;
import com.intrasoft.proddev.referencedata.shared.enums.CodelistKeyEnum;
import java.math.BigInteger;
import java.time.LocalDate;
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.Optional;
import java.util.UUID;
import java.util.stream.Stream;

import org.junit.jupiter.api.DisplayName;
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.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;


@ExtendWith(MockitoExtension.class)
class ExternalSystemValidationServiceUT {

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

    private static final String TRANSIT_EXTERNAL_SYSTEM_VALIDATION_SWITCH_RS = "getTransitExternalSystemValidationSwitchRS";

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

    private final static String MESSAGE_ID = "messageId";

    private final static String EXTERNAL_SYSTEM = "externalSystem";

    private final static String VALIDATION_RESULT_TYPE = "testValidationResultType";

    private ExternalValidationRequestEvent externalValidationRequestEventActual;

    @Mock
    private ErmisLogger ermisLogger;

    @Mock
    private ValidationDDNTASpecificationConfigurationProperties validationDDNTASpecificationConfigurationProperties;

    @Mock
    private TimerDelayExecutor timerDelayExecutor;

    @Mock
    private ErmisConfigurationService ermisConfigurationService;

    @Mock
    private ReferenceDataAdapter referenceDataAdapter;

    @Mock
    private BpmnEngine bpmnEngine;

    @Mock
    private DossierDataService dossierDataService;

    @Mock
    private TransitionPeriodService transitionPeriodService;

    @Mock
    private GetCountryCodeService getCountryCodeService;

    @Mock
    private RuleService ruleService;

    @Spy
    private ValidationResultDTOMapper validationResultDTOMapper = new ValidationResultDTOMapperImpl();

    @Spy
    private final SharedKernelValidationResultMapper sharedKernelValidationResultMapper = new SharedKernelValidationResultMapperImpl();

    @Mock
    private ValidationMessageWrapperBuilder validationMessageWrapperBuilder = new ValidationMessageWrapperBuilder(new MessageHelper(new ArrayList<>()));

    @Spy
    private ExternalSystemValidationRequestProducer externalSystemValidationRequestProducer = new ExternalSystemValidationRequestProducer() {

        @Override
        public void publishExternalSystemValidationRequest(ExternalValidationRequestEvent externalValidationRequestEvent) {

            externalValidationRequestEventActual = externalValidationRequestEvent;
        }
    };

    @InjectMocks
    private ExternalSystemValidationServiceImpl externalSystemValidationService;

    @Captor
    private ArgumentCaptor<CorrelateBpmnMessageCommand> correlateBpmnMessageCommandArgumentCaptor;

    @Captor
    private ArgumentCaptor<MessageDTO> externalValidationResponseEventMessageArgumentCaptor;

    @Captor
    private ArgumentCaptor<Response> responseArgumentCaptor;

    @Captor
    private ArgumentCaptor<String> statusArgumentCaptor;

    @ParameterizedTest
    @ValueSource(booleans = {true, false})
    @DisplayName("Test - Is External System Validation Enabled")
    void testIsExternalSystemValidationEnabled(boolean isExternalSystemValidationEnabled) {

        Configuration configuration = buildConfigurationDTO(isExternalSystemValidationEnabled);

        // Mocks:
        when(ermisConfigurationService.getConfigurationByTemplateName(TRANSIT_EXTERNAL_SYSTEM_VALIDATION_SWITCH_RS))
                .thenReturn(configuration);
        try {
            // Call tested method of service:
            boolean returnedValue = externalSystemValidationService.isExternalSystemValidationEnabled();
            // Assertion:
            assertEquals(isExternalSystemValidationEnabled, returnedValue);
        } catch (Exception e) {

            fail();
        }
    }

    @Test
    @DisplayName("Test - Initiate External System Validation Process")
    void testInitiateExternalSystemValidationProcess() {

        CorrelateBpmnMessageCommand correlateBpmnMessageCommandExpected =
                buildBpmnMessageCommand(BpmnMessageNamesEnum.REQUEST_EXTERNAL_SYSTEM_VALIDATION_MESSAGE.getValue(), PROCESS_BUSINESS_KEY, new HashMap<>());

        try {
            // Call tested method of service:
            externalSystemValidationService.initiateExternalSystemValidationProcess(PROCESS_BUSINESS_KEY, new HashMap<>());
            // Verification & Assertions:
            verify(bpmnEngine, times(1)).correlateMessage(correlateBpmnMessageCommandArgumentCaptor.capture());
            assertEquals(correlateBpmnMessageCommandExpected.getBusinessKey(), correlateBpmnMessageCommandArgumentCaptor.getValue().getBusinessKey());
            assertEquals(correlateBpmnMessageCommandExpected.getMessageName(), correlateBpmnMessageCommandArgumentCaptor.getValue().getMessageName());
            assertEquals(correlateBpmnMessageCommandExpected.getValues(), correlateBpmnMessageCommandArgumentCaptor.getValue().getValues());
        } catch (Exception e) {

            fail();
        }
    }

    @Test
    @DisplayName("Test - Determine External Systems")
    void testDetermineExternalSystems() {

        MessageDTO messageDTO = buildMessageDTO(null, MessageTypes.CC_015_C.value(), null, createIE015Message());

        // Mocks:
        when(dossierDataService.getMessageByMessageId(MESSAGE_ID))
                .thenReturn(messageDTO);
        when(transitionPeriodService.getNCTSTPAndL3EndDates())
                .thenReturn(new Pair<>(LocalDate.now(), LocalDate.now()));
        try {
            // Call tested method of service:
            List<ExternalSystemValidationInstruction> returnedExternalSystemValidationInstructionList =
                    externalSystemValidationService.determineExternalSystems(MESSAGE_ID);
            // Verification & Assertion:
            verify(ruleService, times(1))
                    .executeStateless(eq(BRSessionEnum.EXTERNAL_SYSTEM_INSTRUCTION_IDENTIFICATION_RS.getSessionName()), any(), any());
            assertEquals(0, returnedExternalSystemValidationInstructionList.size());
        } catch (Exception e) {

            fail();
        }
    }

    @ParameterizedTest
    @ValueSource(booleans = {true, false})
    @DisplayName("Test - External System Validation Instructions Exist")
    void testExternalSystemValidationInstructionsExist(boolean externalSystemValidationInstructionsExist) {

        List<ExternalSystemValidationInstruction> externalSystemValidationInstructionList =
                buildExternalSystemValidationInstructionList(externalSystemValidationInstructionsExist);

        try {
            // Call tested method of service:
            boolean returnedValue = externalSystemValidationService.externalSystemValidationInstructionsExist(externalSystemValidationInstructionList);
            // Assertion:
            assertEquals(externalSystemValidationInstructionsExist, returnedValue);
        } catch (Exception e) {

            fail();
        }
    }

    @Test
    @DisplayName("Test - Get External System Response Timer Delay Configuration")
    void testGetExternalSystemResponseTimerDelayConfiguration() {

        TimerInfo expectedTimerInfo = buildTimerInfo();

        // Mocks:
        when(timerDelayExecutor.getTimerDelayConfig(any(), any(), any(), anyBoolean()))
                .thenReturn(Optional.of(expectedTimerInfo));

        try {
            // Call tested method of service:
            TimerInfo timerInfo = externalSystemValidationService.getExternalSystemResponseTimerDelayConfiguration();
            // Assertions:
            assertEquals(expectedTimerInfo.getDelay(), timerInfo.getDelay());
            assertEquals(expectedTimerInfo.getUnit(), timerInfo.getUnit());
            assertEquals(expectedTimerInfo.getRuleInstance(), timerInfo.getRuleInstance());
            assertEquals(expectedTimerInfo.getErrorCode(), timerInfo.getErrorCode());
            assertEquals(expectedTimerInfo.getTimerSetupString(), timerInfo.getTimerSetupString());
        } catch (Exception e) {

            fail();
        }
    }

    @Test
    @DisplayName("Test - Send External Validation Request")
    void testSendExternalValidationRequest() {

        String requestId = UUID.randomUUID().toString();
        String requestMessageId = UUID.randomUUID().toString();
        MessageDTO cc015cMessageDTO = buildMessageDTO(null, MessageTypes.CC_015_C.value(), null, createIE015Message());
        MessageDTO requestMessageDTO = buildMessageDTO(requestMessageId, ExternalValidationRequestEvent.class.getSimpleName(), null, null);
        CustomsData expectedCustomsData = buildCustomsData(cc015cMessageDTO);
        Configuration configuration = buildConfiguration();
        // Mocks:
        when(getCountryCodeService.getCountryCode()).thenReturn("DK");
        when(dossierDataService.getMessageByMessageId(MESSAGE_ID)).thenReturn(cc015cMessageDTO);
        when(dossierDataService.saveMessage(any())).thenReturn(requestMessageDTO);
        when(validationDDNTASpecificationConfigurationProperties.getDDNTASpecification()).thenReturn("DDNTA/5.15");
        when(ermisConfigurationService.getConfigurationByTemplateName("getBundleDeclarationInExternalSystemValidationRS")).thenReturn(configuration);

        try {
            // Call tested method of service:
            externalSystemValidationService.sendExternalValidationRequest(requestId, EXTERNAL_SYSTEM, MESSAGE_ID);
            // Grab produced Customs Data:
            List<CustomsData> customsDataArrayList = new ArrayList<>(externalValidationRequestEventActual.getValidationData());
            CustomsData producedCustomsData = customsDataArrayList.get(0);
            CC015CType expectedPayload = XmlConverter.buildMessageFromXML(cc015cMessageDTO.getPayload(), CC015CType.class);
            CC015CType producedCustomsDataPayload = XmlConverter.buildMessageFromXML(producedCustomsData.getPayload(), CC015CType.class);
            // Verification & Assertions:
            verify(dossierDataService, times(1)).saveMessage(any());
            verify(dossierDataService, times(1)).saveRequest(any());
            verify(externalSystemValidationRequestProducer, times(1)).publishExternalSystemValidationRequest(any());
            // ExternalValidationRequestEvent assertions:
            assertEquals(EXTERNAL_SYSTEM, externalValidationRequestEventActual.getExternalSystem());
            assertEquals(RegimeEnum.TRANSIT.getValue(), externalValidationRequestEventActual.getRegime());
            assertNull(externalValidationRequestEventActual.getBusinessVersion());
            // CustomsData assertions:
            assertEquals(expectedCustomsData.getPayloadSpec(), producedCustomsData.getPayloadSpec());
            assertEquals(expectedCustomsData.getPayloadType(), producedCustomsData.getPayloadType());
            assertEquals(expectedCustomsData.getType(), producedCustomsData.getType());
            assertEquals(expectedCustomsData.getSequenceNumber(), producedCustomsData.getSequenceNumber());
            // CustomsData payload assertions:
            assertEquals(expectedPayload.getMessageSender(), producedCustomsDataPayload.getMessageSender());
            assertEquals(expectedPayload.getMessageRecipient(), producedCustomsDataPayload.getMessageRecipient());
            assertEquals(expectedPayload.getMessageIdentification(), producedCustomsDataPayload.getMessageIdentification());
        } catch (Exception e) {

            fail();
        }
    }

    @Test
    @DisplayName("Test - Handle External System Validation Response")
    void testHandleExternalSystemValidationResponse() {

        String requestId = UUID.randomUUID().toString();

        CorrelateBpmnMessageCommand correlateBpmnMessageCommandExpected =
                buildBpmnMessageCommand(BpmnMessageNamesEnum.EXTERNAL_VALIDATION_RESPONSE_RECEIVED_MESSAGE.getValue(), PROCESS_BUSINESS_KEY, new HashMap<>());
        correlateBpmnMessageCommandExpected.setProcessInstanceId(requestId);

        try {
            // Call tested method of service:
            externalSystemValidationService.handleExternalSystemValidationResponse(PROCESS_BUSINESS_KEY, EXTERNAL_SYSTEM, requestId, false);
            // Verification & Assertions:
            verify(bpmnEngine, times(1)).correlateMessage(correlateBpmnMessageCommandArgumentCaptor.capture());
            assertEquals(correlateBpmnMessageCommandExpected.getMessageName(), correlateBpmnMessageCommandArgumentCaptor.getValue().getMessageName());
            assertEquals(correlateBpmnMessageCommandExpected.getProcessInstanceId(),
                    correlateBpmnMessageCommandArgumentCaptor.getValue().getProcessInstanceId());
            assertTrue(correlateBpmnMessageCommandArgumentCaptor.getValue()
                                                                .getValues()
                                                                .containsKey(
                                                                        BpmnFlowVariablesEnum.EXTERNAL_SYSTEM_VALIDATION_REJECTION_VALUE_CHILD_LOCAL.getValue()));
            assertEquals(Boolean.FALSE, correlateBpmnMessageCommandArgumentCaptor.getValue()
                                                                                 .getValues()
                                                                                 .get(BpmnFlowVariablesEnum.EXTERNAL_SYSTEM_VALIDATION_REJECTION_VALUE_CHILD_LOCAL.getValue()));
        } catch (Exception e) {

            fail();
        }
    }

    @Test
    @DisplayName("Test - Persist External System Validation Results")
    void testPersistExternalSystemValidationResults() {

        List<ValidationResult> validationResultList = buildValidationResultList();

        try {
            // Call tested method of service:
            externalSystemValidationService.persistExternalSystemValidationResults(validationResultList);
            // Verification:
            verify(dossierDataService, times(1)).saveValidationResults(validationResultDTOMapper.toDTO(validationResultList));
        } catch (Exception e) {

            fail();
        }
    }

    @Test
    @DisplayName("Test - Handle Request Timeout")
    void testHandleRequestTimeout() {

        String requestMessageId01 = "requestMessageId01";
        String requestMessageId02 = "requestMessageId02";

        String externalSystem01 = "externalSystem01";
        String externalSystem02 = "externalSystem02";

        List<Request> requestList = new ArrayList<>();
        requestList.add(buildRequest(requestMessageId01));
        requestList.add(buildRequest(requestMessageId02));

        MessageDTO requestMessage01 = buildMessageDTO(requestMessageId01, ExternalValidationRequestEvent.class.getSimpleName(), externalSystem01, null);
        MessageDTO requestMessage02 = buildMessageDTO(requestMessageId02, ExternalValidationRequestEvent.class.getSimpleName(), externalSystem02, null);

        // Mocks:
        when(dossierDataService.getRequestsByCriteria(ArgumentMatchers.isNull(), eq(MESSAGE_ID), eq(ExternalValidationRequestStatusEnum.PENDING.name())))
                .thenReturn(requestList);
        when(dossierDataService.getMessageByMessageId(eq(requestMessageId01))).thenReturn(requestMessage01);
        when(dossierDataService.getMessageByMessageId(eq(requestMessageId02))).thenReturn(requestMessage02);


        try {
            // Call tested method of service:
            externalSystemValidationService.handleRequestTimeout(MESSAGE_ID);
            // Verification:
            verify(dossierDataService, times(1))
                    .getRequestsByCriteria(ArgumentMatchers.isNull(), eq(MESSAGE_ID), eq(ExternalValidationRequestStatusEnum.PENDING.name()));
            verify(dossierDataService, times(1))
                    .getMessageByMessageId(eq(requestMessageId01));
            verify(dossierDataService, times(1))
                    .getMessageByMessageId(eq(requestMessageId02));
            verify(dossierDataService, times(2))
                    .updateRequestStatus(anyString(), eq(ExternalValidationRequestStatusEnum.EXPIRED.name()));
            verify(dossierDataService, times(1))
                    .saveValidationResults(any());
        } catch (Exception e) {

            fail();
        }
    }

    @ParameterizedTest
    @ValueSource(booleans = {true, false})
    @DisplayName("Test - Propagate External System Validation Results")
    void testPropagateExternalSystemValidationResults(boolean containsRejection) {

        List<Boolean> validationRejectionList = buildValidationRejectionMap(containsRejection);

        try {
            // Call tested method of service:
            externalSystemValidationService.propagateExternalSystemValidationResults(MESSAGE_ID, validationRejectionList);
            // Verification:
            verify(bpmnEngine, times(1)).correlateMessage(correlateBpmnMessageCommandArgumentCaptor.capture());
            // Assertions:
            assertEquals(MESSAGE_ID, correlateBpmnMessageCommandArgumentCaptor.getValue().getBusinessKey());
            assertEquals("externalSystemValidationCompletedMessage", correlateBpmnMessageCommandArgumentCaptor.getValue().getMessageName());
            assertTrue(correlateBpmnMessageCommandArgumentCaptor.getValue().getValues().containsKey(BpmnFlowVariablesEnum.IS_REJECTING.getValue()));
            assertEquals(containsRejection,
                    correlateBpmnMessageCommandArgumentCaptor.getValue().getValues().get(BpmnFlowVariablesEnum.IS_REJECTING.getValue()));
        } catch (Exception e) {

            fail();
        }
    }

    @ParameterizedTest
    @ValueSource(booleans = {true, false})
    @DisplayName("Test - Map To Transit Validation Result List")
    void testMapToTransitValidationResultList(boolean isRejecting) {

        // Mocks:
        when(referenceDataAdapter.verifyCodeListItem(eq(CodelistKeyEnum.VALIDATION_RESULT_TYPES.getKey()), eq(VALIDATION_RESULT_TYPE),
                (LocalDate) any(), any(), (CodeListDomain) any())).thenReturn(true);
        when(referenceDataAdapter.verifyCodeListItem(eq(CodelistKeyEnum.REJECTING_VALIDATION_RESULTS.getKey()), eq(VALIDATION_RESULT_TYPE),
                (LocalDate) any(), any(), (CodeListDomain) any())).thenReturn(isRejecting);

        List<ValidationResultDTO> externalValidationResultList = buildExternalValidationResultList(false);

        try {
            // Call tested method of service:
            List<ValidationResult> mappedExternalValidationResults =
                    externalSystemValidationService.mapToTransitValidationResultList(externalValidationResultList, MESSAGE_ID);
            // Assertions:
            assertEquals(MESSAGE_ID, mappedExternalValidationResults.get(0).getMessageId());
            assertEquals(externalValidationResultList.get(0).getViolatedRule().getRuleID(), mappedExternalValidationResults.get(0).getRuleId());
            assertEquals(isRejecting, mappedExternalValidationResults.get(0).isRejecting());
            assertEquals(externalValidationResultList.get(0).getPointers().get(0), mappedExternalValidationResults.get(0).getPointer());
            assertEquals(externalValidationResultList.get(0).getInformation().get(0).getCode(),
                    mappedExternalValidationResults.get(0).getErrorCode());
        } catch (Exception e) {

            fail();
        }
    }

    @ParameterizedTest
    @ValueSource(booleans = {true, false})
    @DisplayName("Test - Map To Transit Validation Result List with Missing Data")
    void testMapToTransitValidationResultListWithMissingData(boolean isRejecting) {

        // Mocks:
        when(referenceDataAdapter.verifyCodeListItem(eq(CodelistKeyEnum.VALIDATION_RESULT_TYPES.getKey()), eq(VALIDATION_RESULT_TYPE), (LocalDate) any(), any(), (CodeListDomain) any())).thenReturn(true);
        when(referenceDataAdapter.verifyCodeListItem(eq(CodelistKeyEnum.REJECTING_VALIDATION_RESULTS.getKey()), eq(VALIDATION_RESULT_TYPE), (LocalDate) any(), any(), (CodeListDomain) any())).thenReturn(isRejecting);

        List<ValidationResultDTO> externalValidationResultList = buildExternalValidationResultList(true);

        try {
            // Call tested method of service:
            List<ValidationResult> mappedExternalValidationResults =
                    externalSystemValidationService.mapToTransitValidationResultList(externalValidationResultList, MESSAGE_ID);
            // Assertions:
            assertEquals(MESSAGE_ID, mappedExternalValidationResults.get(0).getMessageId());
            assertEquals(externalValidationResultList.get(0).getViolatedRule().getRuleID(), mappedExternalValidationResults.get(0).getRuleId());
            assertEquals(isRejecting, mappedExternalValidationResults.get(0).isRejecting());
            assertEquals("", mappedExternalValidationResults.get(0).getPointer());
            assertEquals("", mappedExternalValidationResults.get(0).getErrorCode());
        } catch (Exception e) {

            fail();
        }
    }

    @ParameterizedTest
    @ValueSource(booleans = {true, false})
    @DisplayName("Test - Is Response Expected")
    void testIsResponseExpected(boolean responseExpected) {

        String requestId = UUID.randomUUID().toString();
        Request request = buildRequest(UUID.randomUUID().toString());
        if (!responseExpected) {
            request.setStatus(ExternalValidationRequestStatusEnum.CANCELLED.name());
        }
        // Mock:
        when(dossierDataService.getRequestById(eq(requestId))).thenReturn(request);

        try {
            // Call tested method of service:
            boolean returnedValue = externalSystemValidationService.isResponseExpected(requestId);
            // Verification:
            verify(dossierDataService, times(1)).getRequestById(eq(requestId));
            // Assertion:
            assertEquals(responseExpected, returnedValue);
        } catch (Exception e) {

            fail();
        }
    }

    @Test
    @DisplayName("Test - Persist External System Validation Response")
    void testPersistExternalSystemValidationResponse() {

        String responseMessageId = UUID.randomUUID().toString();
        String externalSystem = "EXTERNAL_SYSTEM";

        ExternalValidationResponseEvent externalValidationResponseEvent = buildExternalValidationResponseEvent(MESSAGE_ID, externalSystem);

        MessageDTO expectedResponseMessageDTO = buildMessageDTO(responseMessageId, ExternalValidationResponseEvent.class.getSimpleName(),
                externalSystem, externalValidationResponseEvent);
        // Mock:
        when(getCountryCodeService.getCountryCode()).thenReturn("DK");
        when(dossierDataService.saveMessage(any())).thenReturn(expectedResponseMessageDTO);

        try {
            // Call tested method of service:
            externalSystemValidationService.persistExternalSystemValidationResponse(externalValidationResponseEvent);
            // Verification:
            verify(dossierDataService, times(1)).saveMessage(externalValidationResponseEventMessageArgumentCaptor.capture());
            // Assertions:
            assertEquals(expectedResponseMessageDTO.getMessageType(), externalValidationResponseEventMessageArgumentCaptor.getValue().getMessageType());
            assertEquals(expectedResponseMessageDTO.getPayload(), externalValidationResponseEventMessageArgumentCaptor.getValue().getPayload());
            assertEquals(expectedResponseMessageDTO.getSource(), externalValidationResponseEventMessageArgumentCaptor.getValue().getSource());
            assertEquals(expectedResponseMessageDTO.getDestination(), externalValidationResponseEventMessageArgumentCaptor.getValue().getDestination());
            assertEquals(expectedResponseMessageDTO.getDomain(), externalValidationResponseEventMessageArgumentCaptor.getValue().getDomain());
            assertEquals(expectedResponseMessageDTO.getMessageIdentification(), externalValidationResponseEventMessageArgumentCaptor.getValue().getMessageIdentification());
            assertEquals(expectedResponseMessageDTO.getDirection(), externalValidationResponseEventMessageArgumentCaptor.getValue().getDirection());
        } catch (Exception e) {

            fail();
        }
    }

    @ParameterizedTest
    @ValueSource(booleans = {true, false})
    @DisplayName("Test - Persist Response")
    void testPersistResponse(boolean finalResponse) {

        String responseId = UUID.randomUUID().toString();
        String requestId = UUID.randomUUID().toString();
        String responseMessageId = UUID.randomUUID().toString();

        try {
            // Call tested method of service:
            externalSystemValidationService.persistResponse(responseId, requestId, responseMessageId, finalResponse);
            // Verification:
            verify(dossierDataService, times(1)).saveResponse(responseArgumentCaptor.capture());
            // Assertions:
            assertEquals(responseId, responseArgumentCaptor.getValue().getId());
            assertEquals(requestId, responseArgumentCaptor.getValue().getRequestId());
            assertEquals(responseMessageId, responseArgumentCaptor.getValue().getResponseMessageId());
            assertEquals(finalResponse, responseArgumentCaptor.getValue().isFinalResponse());
        } catch (Exception e) {

            fail();
        }
    }

    @ParameterizedTest
    @MethodSource("provideStatusArguments")
    @DisplayName("Test - Update Request Status")
    void testUpdateRequestStatus(String status) {

        String requestId = UUID.randomUUID().toString();

        try {
            // Call tested method of service:
            externalSystemValidationService.updateRequestStatus(requestId, status);
            // Verification:
            verify(dossierDataService, times(1)).updateRequestStatus(eq(requestId), statusArgumentCaptor.capture());
            // Assertion:
            assertEquals(status, statusArgumentCaptor.getValue());
        } catch (Exception e) {

            fail();
        }
    }

    private Request buildRequest(String requestMessageId) {

        return Request.builder()
                      .id(UUID.randomUUID().toString())
                      .requestMessageId(requestMessageId)
                      .referencedMessageId(MESSAGE_ID)
                      .status(ExternalValidationRequestStatusEnum.PENDING.name())
                      .build();
    }

    private <T> MessageDTO buildMessageDTO(String id, String messageType, String externalSystem, T payload) {

        if (ExternalValidationRequestEvent.class.getSimpleName().equals(messageType)) {

            return MessageDTO.builder()
                             .id(id)
                             .messageIdentification(MESSAGE_ID)
                             .messageType(messageType)
                             .direction(DirectionEnum.SENT.getDirection())
                             .destination(externalSystem)
                             .build();
        } else if (ExternalValidationResponseEvent.class.getSimpleName().equals(messageType)) {

            return MessageDTO.builder()
                             .id(id)
                             .messageIdentification(MESSAGE_ID)
                             .messageType(messageType)
                             .source(externalSystem)
                             .payload(JSON_CONVERTER.convertToJsonString(payload))
                             .destination(MessageTypeMapper.NTA + "DK")
                             .domain(MessageDomainTypeEnum.ERMIS.name())
                             .direction(DirectionEnum.RECEIVED.getDirection())
                             .build();
        } else {

            return MessageDTO.builder()
                             .id(id == null ? UUID.randomUUID().toString() : id)
                             .messageIdentification(MESSAGE_ID)
                             .messageType(messageType)
                             .payload(XmlConverter.marshal(payload, NamespacesEnum.NTA.getValue(), MessageTypes.CC_015_C.value()))
                             .build();
        }
    }

    private CodeListItem buildCodeListItem() {

        List<Attribute> attributeList = new ArrayList<>();
        Attribute TPEndDateAttribute = Attribute.builder().key("TPendDate").value("2025-12-25 12:00:00").build();
        Attribute L3EndDateAttribute = Attribute.builder().key("L3endDate").value("2025-12-25 12:00:00").build();

        attributeList.add(TPEndDateAttribute);
        attributeList.add(L3EndDateAttribute);

        return CodeListItem.builder().code("TEST_CODE").attributes(attributeList).build();
    }

    private Configuration buildConfigurationDTO(boolean isEnabled) {

        Map<String, String> evaluationParams = new HashMap<>();
        if (isEnabled) {
            evaluationParams.put("switchValue", "enabled");
        } else {
            evaluationParams.put("switchValue", "disabled");
        }

        return new Configuration(
                null,
                null,
                null,
                evaluationParams,
                null,
                LocalDateTime.now(ZoneOffset.UTC).minusDays(60),
                LocalDateTime.now(ZoneOffset.UTC).minusDays(60),
                LocalDateTime.now(ZoneOffset.UTC).plusDays(60));
    }

    private CorrelateBpmnMessageCommand buildBpmnMessageCommand(String messageName, String processBusinessKey, Map<String, Object> values) {

        return CorrelateBpmnMessageCommand
                .builder()
                .messageName(messageName)
                .businessKey(processBusinessKey)
                .values(values)
                .build();
    }

    private List<ExternalSystemValidationInstruction> buildExternalSystemValidationInstructionList(boolean isPopulated) {

        List<ExternalSystemValidationInstruction> externalSystemValidationInstructionList = new ArrayList<>();

        if (isPopulated) {
            ExternalSystemValidationInstruction externalSystemValidationInstruction = new ExternalSystemValidationInstruction();
            externalSystemValidationInstruction.setExternalSystem("TEST_SYSTEM");
            externalSystemValidationInstructionList.add(externalSystemValidationInstruction);
        }

        return externalSystemValidationInstructionList;
    }

    private TimerInfo buildTimerInfo() {

        TimerInfo timerInfo = new TimerInfo();
        timerInfo.setDelay(60);
        timerInfo.setUnit(TimeUnitEnum.MINUTES);
        timerInfo.setErrorCode("TEST_ERROR_CODE");
        timerInfo.setRuleInstance("TEST_RULE_INSTANCE");

        return timerInfo;
    }

    private List<ValidationResult> buildValidationResultList() {

        List<ValidationResult> validationResultList = new ArrayList<>();

        ValidationResult validationResult = ValidationResult.builder().messageId(MESSAGE_ID).build();

        validationResultList.add(validationResult);

        return validationResultList;
    }

    private List<Boolean> buildValidationRejectionMap(Boolean containsRejection) {

        List<Boolean> validationRejectionList = new ArrayList<>();

        validationRejectionList.add(false);
        validationRejectionList.add(false);

        if (containsRejection) {
            validationRejectionList.add(true);
        }

        return validationRejectionList;
    }

    private CustomsData buildCustomsData(MessageDTO messageDTO) {

        CustomsData customsData = new CustomsData();

        customsData.setSequenceNumber(BigInteger.ONE);
        customsData.setPayloadSpec("DDNTA/5.15");
        customsData.setType(messageDTO.getMessageType());
        customsData.setPayloadType(CustomsDataPayloadContentTypeEnum.XML.toString());
        customsData.setPayload(messageDTO.getPayload());

        return customsData;
    }

    private Configuration buildConfiguration() {

        Configuration configuration = new Configuration(
                "getBundleDeclarationInExternalSystemValidationRS",
                "ruleCode",
                new HashMap<>(),
                new HashMap<>(),
                "errorCode",
                LocalDateTime.now(ZoneOffset.UTC),
                LocalDateTime.now(ZoneOffset.UTC),
                LocalDateTime.now(ZoneOffset.UTC)
        );
        // Add evaluation parameters:
        configuration.getEvaluationParams().put("enabled", "false");

        return configuration;
    }

    private List<ValidationResultDTO> buildExternalValidationResultList(boolean missingData) {

        List<ValidationResultDTO> validationResultDTOList = new ArrayList<>();
        List<ValidationInformationDTO> validationInformationDTOList = new ArrayList<>();
        List<String> pointerList = new ArrayList<>();

        RuleDTO ruleDTO = new RuleDTO();
        ruleDTO.setRuleID("TEST_RULE_ID");

        if (!missingData) {
            ValidationInformationDTO validationInformationDTO = new ValidationInformationDTO();
            validationInformationDTO.setCode("TEST_CODE");
            validationInformationDTO.setText("TEST_TEXT");
            validationInformationDTO.setValidationInformationType("TEST_VALIDATION_INFORMATION_TYPE");
            validationInformationDTOList.add(validationInformationDTO);
        }

        if (!missingData) {
            String pointer = "TEST_POINTER";
            pointerList.add(pointer);
        }

        ValidationResultDTO validationResultDTO = new ValidationResultDTO();
        validationResultDTO.setValidationResultType(VALIDATION_RESULT_TYPE);
        validationResultDTO.setViolatedRule(ruleDTO);
        validationResultDTO.setInformation(validationInformationDTOList);
        validationResultDTO.setPointers(pointerList);

        validationResultDTOList.add(validationResultDTO);

        return validationResultDTOList;
    }

    private ExternalValidationResponseEvent buildExternalValidationResponseEvent(String businessKey, String externalSystem) {

        ExternalValidationResponseEvent externalValidationResponseEvent = new ExternalValidationResponseEvent();
        externalValidationResponseEvent.setBusinessKey(businessKey);
        externalValidationResponseEvent.setExternalSystem(externalSystem);

        return externalValidationResponseEvent;
    }

    private static Stream<Arguments> provideStatusArguments() {

        return Stream.of(
                Arguments.of(ExternalValidationRequestStatusEnum.PENDING.name()),
                Arguments.of(ExternalValidationRequestStatusEnum.FAILED.name()),
                Arguments.of(ExternalValidationRequestStatusEnum.CANCELLED.name()),
                Arguments.of(ExternalValidationRequestStatusEnum.COMPLETED.name()),
                Arguments.of(ExternalValidationRequestStatusEnum.EXPIRED.name())
        );
    }
}
