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

import static java.text.MessageFormat.format;

import com.intrasoft.ermis.common.logging.ErmisLogger;
import com.intrasoft.ermis.common.xml.parsing.XmlConverter;
import com.intrasoft.ermis.ddntav5152.messages.CC015CType;
import com.intrasoft.ermis.ddntav5152.messages.CD003CType;
import com.intrasoft.ermis.ddntav5152.messages.MessageTypes;
import com.intrasoft.ermis.transit.common.enums.DirectionEnum;
import com.intrasoft.ermis.transit.common.enums.MessageDomainTypeEnum;
import com.intrasoft.ermis.transit.common.enums.OfficeRoleTypesEnum;
import com.intrasoft.ermis.transit.common.exceptions.BaseException;
import com.intrasoft.ermis.transit.common.exceptions.NotFoundException;
import com.intrasoft.ermis.transit.management.core.exception.BadRequestException;
import com.intrasoft.ermis.transit.management.core.port.outbound.DossierDataService;
import com.intrasoft.ermis.transit.management.core.service.ConfigurationSwitchService;
import com.intrasoft.ermis.transit.management.domain.Declaration;
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.management.domain.enums.PrintType;
import com.intrasoft.ermis.transit.management.domain.enums.TADVersionEnum;
import com.intrasoft.ermis.transit.management.domain.report.CC029CRootRM;
import com.intrasoft.ermis.transit.management.domain.report.PrintConfiguration;
import com.intrasoft.ermis.transit.management.domain.report.TemplateConfiguration;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.DateTimeException;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
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;

@Service
@RequiredArgsConstructor
public class ReportServiceImpl implements ReportService {

    /* @formatter:off */
    public static final Map<com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum, Set<MessageTypes>>
            VALID_TAD_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE = new EnumMap<>(
            Map.of(
                    com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum.DEPARTURE,   Set.of(MessageTypes.CC_029_C),
                    com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum.DESTINATION, Set.of(MessageTypes.CD_001_C, MessageTypes.CD_003_C),
                    com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum.EXIT,        Set.of(MessageTypes.CD_160_C, MessageTypes.CD_165_C),
                    com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum.TRANSIT,     Set.of(MessageTypes.CD_050_C, MessageTypes.CD_115_C),
                    com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum.INCIDENT,    Set.of(MessageTypes.CD_038_C),
                    com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum.LODGEMENT, Set.of(MessageTypes.CC_015_C)
            ));
    /* @formatter:on */

    protected static final EnumMap<com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum, Set<MessageTypes>>
            VALID_NP_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE = new EnumMap<>(
            Map.of(
                    com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum.DEPARTURE, Set.of(MessageTypes.CC_013_C, MessageTypes.CC_015_C)
            ));

    protected static final Set<MessageTypes> VALID_TAD_REPORT_MESSAGE_TYPES =
            VALID_TAD_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE.values().stream().flatMap(Set::stream).collect(Collectors.toSet());

    protected static final Set<MessageTypes> VALID_NP_REPORT_MESSAGE_TYPES =
            VALID_NP_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE.values().stream().flatMap(Set::stream).collect(Collectors.toSet());

    public static final String SECURITY_0 = "0";

    public static final String JRXML_CC029C = "CC029C";

    private static final String TEMPLATE_RESOURCE_URL = "reports/iemessages/{0}/p5/Master.jrxml";

    private static final String TRANSLATIONS_RESOURCE_URL = "reports.iemessages.i18n.IEMessages";

    private static final String TEMPLATE_RESOURCE_URL_P4 = "reports/iemessages/{0}/p4/tad/TAD.jrxml";

    private static final String TEMPLATE_TSAD_RESOURCE_URL_P4 = "reports/iemessages/{0}/p4/tad/TAD_SEC.jrxml";

    private static final String TRANSLATIONS_RESOURCE_URL_P4 = "reports.iemessages.i18n.TAD_P4";

    public static final String JRXML_NP_RESOURCE_BASE_URL = "reports/iemessages/{0}/Master.jrxml";

    public static final String MASTER_JRXML_RESOURCE_BUNDLE = "reports.iemessages.i18n.IEMessages";


    // ****************  Dependencies ************************
    private final DossierDataService dossierDataService;

    private final CC029CPrintDataAggregator cc029CPrintDataAggregator;

    private final CC015CPrintDataAggregator cc015CPrintDataAggregator;

    private final PrintService printer;

    private final P4PrintModelMapper p4PrintModelMapper;

    private final ErmisLogger ermisLogger;

    private final ReportMessageTranslatorService reportMessageTranslatorService;

    private final ConfigurationSwitchService configurationSwitchService;

    @Override
    public byte[] printMessage(String mrn, String officeRoleType, String messageId, PrintType printType, String language, String country, String datePattern,
            String timezone, String version, String security) throws BadRequestException {

        var printConfiguration =
                validatePrintConfiguration(PrintConfiguration.builder()
                                                             .printType(printType)
                                                             .language(language)
                                                             .country(country)
                                                             .datePattern(datePattern)
                                                             .timezone(timezone)
                                                             .username("")
                                                             .build());
        validateOfficeRoleType(officeRoleType);

        ermisLogger.debug("Will print IE message with id '{}' as {} (the relevant MRN is '{}').", messageId, printConfiguration.getPrintType().name(), mrn);

        Message originalMessage;
        if (StringUtils.isNotBlank(messageId)) {
            // If the [messageId] param has been provided in the Rest endpoint, then fetch the Message directly by Message ID.
            // Optimised approach, used on Office of DEPARTURE with CC029C
            originalMessage = dossierDataService.getMessageById(messageId, false);
        } else if (StringUtils.isBlank(officeRoleType)) {
            // If only the [MRN] param has been provided, assume that the request is from DEPARTURE and the message type is CC029C
            originalMessage = getMessageByMRN(mrn);
            if (originalMessage == null) {
                throw new NotFoundException(
                        String.format(
                                "Couldn't find message for MRN: [%s],  default Office Role Type: [DEPARTURE] and default Message Type: [CC029C]. Cannot generate TAD document!",
                                mrn));
            }
        } else {
            // Finally if [MRN] and [officeRoleType] params have been provided, search into the Dossier for the suitable message.
            // Non optimised approach. Multiple Dossier calls involved
            originalMessage = getMessageByMRNAndOfficeRoleType(mrn, null, officeRoleType, VALID_TAD_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE);
            if (originalMessage == null) {
                throw new NotFoundException(
                        String.format("Couldn't find message for MRN: [%s],  Office Role Type: [%s] and Message Type in: %s. Cannot generate TAD document!",
                                mrn, officeRoleType, printReportMessageTypesForOfficeRoleType(officeRoleType)));
            }

        }

        // Call relevant print method.
        Object printModel = cc029CPrintDataAggregator.aggregatePrintData(mrn,
                reportMessageTranslatorService.generateCC029CTypeMessageFromOriginalMessage(originalMessage));

        String finalVersion = decideTadVersion(ReportMessageTranslatorImpl.findMessageType(originalMessage), version);
        if (TADVersionEnum.P4.getVersion().equals(finalVersion)) {
            printConfiguration.setTemplate(
                    new TemplateConfiguration(format(computeP4JrxmlResourcePath(security), MessageTypes.CC_029_C.value()), TRANSLATIONS_RESOURCE_URL_P4));
            printModel = p4PrintModelMapper.factoringDeclarationPrintDataTO((CC029CRootRM) printModel);
        } else {
            printConfiguration.setTemplate(new TemplateConfiguration(format(TEMPLATE_RESOURCE_URL, MessageTypes.CC_029_C.value()), TRANSLATIONS_RESOURCE_URL));
        }

        byte[] bytes = printer.print(printConfiguration, printModel);

        saveTADToDisk(originalMessage.getMessageType(), "TAD",  mrn, bytes);

        ermisLogger.info("Generated TAD document from message with id '{}' and type [{}] (the relevant MRN is '{}') from message type: [{}]",
                originalMessage.getMessageIdentification(),
                printConfiguration.getPrintType().name(), mrn, originalMessage.getMessageType());

        return bytes;
    }

    @Override
    public byte[] printNationalPrintMessage(String mrn, String lrn, String officeRoleType, String messageId, PrintType printType, String language,
            String country, String datePattern, String timezone, String version) throws BadRequestException, JAXBException {
        var printConfiguration = validatePrintConfiguration(PrintConfiguration.builder()
                                                                              .printType(printType)
                                                                              .language(language)
                                                                              .country(country)
                                                                              .datePattern(datePattern)
                                                                              .timezone(timezone)
                                                                              .username("")
                                                                              .template(getNationalPrintTemplate())
                                                                              .build());
        validateOfficeRoleType(officeRoleType);

        ermisLogger.debug("Will print IE message with id '{}' as {} (the relevant MRN is '{}').", messageId, printConfiguration.getPrintType().name(), mrn);
        mrn = cleanInput(mrn);
        lrn = cleanInput(lrn);

        String finalMrn = mrn;
        Movement movement = dossierDataService
                .findByCriteria(mrn, lrn, officeRoleType, null, 2)
                .stream()
                .findFirst()
                .orElseThrow(() ->
                        new NotFoundException("No DEPARTURE Movement found for mrn: [" + finalMrn + "]"));

        Declaration declaration = dossierDataService.getLatestDeclarationByMovementId(movement.getId());

        CC015CType cc015cTypeMessage = XmlConverter.buildMessageFromXML(declaration.getPayload(), CC015CType.class);

        Message originalMessage = Message.builder()
                                         .messageIdentification(cc015cTypeMessage.getMessageIdentification())
                                         .messageType(cc015cTypeMessage.getMessageType().value())
                                         .source(cc015cTypeMessage.getMessageSender())
                                         .destination(cc015cTypeMessage.getMessageRecipient())
                                         .domain(MessageDomainTypeEnum.EXTERNAL.name())
                                         .payload(declaration.getPayload())
                                         .direction(DirectionEnum.SENT.getDirection())
                                         .build();

        Object printModel = cc015CPrintDataAggregator.aggregatePrintData(mrn, lrn,
                originalMessage);
        byte[] bytes = printer.print(printConfiguration, printModel);

        saveTADToDisk(originalMessage.getMessageType(), "NATIONAL_PRINT" , mrn, bytes);

        ermisLogger.info("Generated NP document from message with id '{}' and type [{}] (the relevant MRN is '{}') from message type: [{}]",
                originalMessage.getMessageIdentification(), printConfiguration.getPrintType().name(), mrn, originalMessage.getMessageType());

        return bytes;
    }

    @SneakyThrows
    public Message getMessageByMRNAndOfficeRoleType(String mrn, String lrn, String officeRoleType,
            Map<com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum, Set<MessageTypes>> officeRolesTypesMap) {
        com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum
                officeRoleTypesEnum = Objects.requireNonNull(com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum.of(officeRoleType));
        for (MessageTypes messageTypes : officeRolesTypesMap.get(officeRoleTypesEnum)) {
            var message = findLastMessageByMrnOfficeRoleTypeAndMessageType(mrn, lrn, officeRoleType, messageTypes.value());
            if (message != null) {
                if (message.getMessageType().equals(MessageTypes.CD_003_C.value())) {
                    // Special treatment on CD003C. Needs to be a 'positive' CD003C in order to be considered for TAD Generation
                    CD003CType cd003CType = XmlConverter.buildMessageFromXML(message.getPayload(), CD003CType.class);
                    String requestRejectedReasonCode = cd003CType.getTransitOperation().getRequestRejectionReasonCode();
                    if (StringUtils.isNotBlank(requestRejectedReasonCode)) {
                        ermisLogger.info(
                                "CD003C message with id: [{}] for MRN: [{}] and OfficeRoleType: [{}] is not POSITIVE with requestRejectionReasonCode=[{}]. Will not be considered for TAD Generation",
                                message.getMessageIdentification(), mrn, officeRoleType, requestRejectedReasonCode);
                        continue;
                    } else {
                        return message;
                    }
                }
                ermisLogger.info("Found suitable message with Id: [{}] and Type: [{}] for TAD generation on MRN: [{}] and OfficeRoleType: [{}]",
                        message.getId(), message.getMessageType(), mrn, officeRoleType);
                return message;
            }
        }
        return null;
    }

    /**
     * @return : Message if found or NULL if not found
     */
    public Message findLastMessageByMrnOfficeRoleTypeAndMessageType(String mrn, String lrn, String officeRoleType, String messageType) {
        ermisLogger.info("findLastMessageByMrnOfficeRoleTypeAndMessageType w mrn: {}, lrn: {}, officeRoleType: {}", mrn, lrn, officeRoleType);
        // Step 1.) -> Find the MOVEMENT record by MRN and OFFICE_ROLE_TYPE
        Movement movement =
                dossierDataService
                        .findByCriteria(mrn, lrn, officeRoleType, null, 2)
                        .stream()
                        .findFirst()
                        .orElseThrow(() -> new NotFoundException(
                                String.format("Couldn't find movement for MRN: [%s], LRN: [%s] Office RoleType: [%s]. Cannot generate TAD document",
                                        mrn, lrn, officeRoleType)));

        // Step 2.) -> Find the Message ID by Movement Id and Message Type
        String messageId = findMessageIdByMovementIdAndMessageType(movement.getId(), messageType);
        return messageId != null ? dossierDataService.getMessageById(messageId, false) : null;
    }

    /**
     * @return : Message if found or NULL if not found
     */
    private Message getMessageByMRN(String mrn) {
        Movement movement = dossierDataService
                .findByCriteria(mrn, null, OfficeRoleTypesEnum.DEPARTURE.getValue(), null, 2)
                .stream()
                .findFirst()
                .orElseThrow(() ->
                        new NotFoundException("No DEPARTURE Movement found for mrn: [" + mrn + "]"));

        String messageId = findMessageIdByMovementIdAndMessageType(movement.getId(), MessageTypes.CC_029_C.value());
        return messageId != null ? dossierDataService.getMessageById(messageId, false) : null;
    }

    /**
     * @return : MOVEMENT.ID if movement found , NULL if Movement not found
     */
    public String findMessageIdByMovementIdAndMessageType(String movementId, String messageType) {
        return dossierDataService
                .getMessagesOverview(messageType, movementId, false)
                .stream()
                .findFirst()
                .orElseGet(() -> MessageOverview.builder().build())
                .getId();
    }

    private String decideTadVersion(MessageTypes originalMessageType, String version) {
        if (MessageTypes.CC_029_C == originalMessageType) {
            return version;
        } else if (MessageTypes.CC_015_C == originalMessageType) {
            return TADVersionEnum.P5.getVersion();
        } else {
            return retrieveTadVersionFromBusinessRule();
        }
    }

    String retrieveTadVersionFromBusinessRule() {
        return translateTadVersionValueIfNeeded(configurationSwitchService.retrieveTADVersion());
    }

    /**
     * Rule Instance parameter [TADVersion] values are stored as 'P4' or 'P5' in the Configuration database. Convert them to "NCTS_P4" or "NCTS_P5" to match the
     * {@link TADVersionEnum#getVersion}
     */
    private static String translateTadVersionValueIfNeeded(String value) {
        if (value.contains("P4")) {
            return TADVersionEnum.P4.getVersion();
        } else if (value.contains("P5")) {
            return TADVersionEnum.P5.getVersion();
        } else {
            throw new BaseException(String.format("Unknown Value: [%s] returned from Rule: [%s]", value, ConfigurationSwitchService.GET_TAD_VERSION_RS));
        }
    }

    /**
     * <pre>
     * Useful on Local Development where WEB UI is not available and there is no "OPEN/SAVE AS" functionality via a web browser to view the PDF document
     * Add an extra VM option [-Dcwm.api-gateway.saveTadToDisk=true] in the Spring Boot Run/Debug Configuration
     * The generated PDF Document will be saved locally in the OS default temporary-file directory
     * Double-click on the file and view the PDF content
     */
    @SuppressWarnings("findsecbugs:PATH_TRAVERSAL_IN")
    private void saveTADToDisk(String originalMessageType, String reportType, String mrn, byte[] bytes) {
        if ("true".equalsIgnoreCase(System.getProperty("cwm.api-gateway.saveTadToDisk"))) {
            try {
                Path root = Files.createTempDirectory(".."  + reportType + "_");
                var path = Paths.get(root.toFile().getAbsolutePath(),
                        System.currentTimeMillis() + "_" + originalMessageType + "_" + mrn + ".pdf");
                Files.write(path, bytes);
                ermisLogger.info(":=> Saved " + reportType + " PDF into: [" + path.toAbsolutePath() + "]");
            } catch (IOException e) {
                ermisLogger.error("Could not save " + reportType + " document to disk!!", e);
            }
        }
    }

    public static String printReportMessageTypesForOfficeRoleType(String officeRoleType) {
        return VALID_TAD_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE
                .get(Objects.requireNonNull(com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum.of(officeRoleType)))
                .stream()
                .map(messageType -> "[" + (messageType != MessageTypes.CC_029_C ? messageType.value() : "CC_029_C") + "]")
                .collect(Collectors.joining(","));
    }

    // ****************************  Validators *******************************
    private static PrintConfiguration validatePrintConfiguration(PrintConfiguration printConfig) throws BadRequestException {
        if (printConfig == null) {
            return PrintConfiguration.createDefault();
        }

        var pc = new PrintConfiguration();

        pc.setPrintType(printConfig.getPrintType());
        pc.setLanguage(validateLanguage(printConfig.getLanguage()));
        pc.setCountry(validateCountry(printConfig.getCountry()));
        pc.setDatePattern(validateDatePattern(printConfig.getDatePattern()));
        pc.setTimezone(validateTimezone(printConfig.getTimezone()));
        pc.setUsername(printConfig.getUsername());
        pc.setTemplate(printConfig.getTemplate());

        return pc;

    }

    private static String validateTimezone(String timezone) throws BadRequestException {
        try {
            ZoneId.of(timezone);
            return timezone;
        } catch (DateTimeException | NullPointerException ex) {
            throw new BadRequestException(List.of(Violation.builder().reason("Invalid timezone " + timezone).build()));
        }
    }

    private static String validateLanguage(String language) throws BadRequestException {

        Set<String> languages = Set.of(Locale.getISOLanguages());

        if (language == null || language.isBlank() || !languages.contains(language.toLowerCase(Locale.US))) {
            throw new BadRequestException(List.of(Violation.builder().reason("Invalid language " + language).build()));
        }

        return language.toLowerCase(Locale.US);
    }

    private static String validateCountry(String country) throws BadRequestException {
        Set<String> countries = Set.of(Locale.getISOCountries());
        if (country == null || country.isBlank() || !countries.contains(country.toUpperCase(Locale.US))) {
            throw new BadRequestException(List.of(Violation.builder().reason("Invalid country: " + country).build()));
        }

        return country.toUpperCase(Locale.US);
    }

    private static String validateDatePattern(String datePattern) throws BadRequestException {
        try {
            DateTimeFormatter.ofPattern(datePattern);
            return datePattern;
        } catch (IllegalArgumentException | NullPointerException ex) {
            throw new BadRequestException(List.of(Violation.builder().reason("Invalid datePattern: " + datePattern).build()));
        }
    }

    public static void validateOfficeRoleType(String officeRoleType) throws BadRequestException {
        if (StringUtils.isNotBlank(officeRoleType)) {
            com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum officeRoleTypesEnum =
                    Objects.requireNonNull(com.intrasoft.ermis.platform.contracts.dossier.enums.OfficeRoleTypesEnum.of(officeRoleType),
                            String.format("Unknown OfficeRoleType: [%s]. Cannot generate TAD document", officeRoleType));
            if (!VALID_TAD_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE.containsKey(officeRoleTypesEnum)) {
                throw new BadRequestException(
                        List.of(
                                Violation.builder()
                                         .reason(String.format("Invalid OfficeRoleType: [%s]. Not supported for TAD Generation", officeRoleType))
                                         .build()));
            }
        }
    }

    private static TemplateConfiguration getNationalPrintTemplate() {
        return new TemplateConfiguration(format(JRXML_NP_RESOURCE_BASE_URL, "np"), MASTER_JRXML_RESOURCE_BUNDLE);
    }

    private static String cleanInput(String input) {
        if ("null".equals(input)) {
            input = null;
        }
        return input;
    }

    private String computeP4JrxmlResourcePath(String security) {
        if (SECURITY_0.equals(security)) {
            return format(TEMPLATE_RESOURCE_URL_P4, JRXML_CC029C);
        } else {
            return format(TEMPLATE_TSAD_RESOURCE_URL_P4, JRXML_CC029C);
        }
    }
}

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

import com.intrasoft.ermis.common.logging.ErmisLogger;
import com.intrasoft.ermis.transit.management.core.port.outbound.DossierDataService;
import com.intrasoft.ermis.transit.management.core.service.report.ReportServiceImpl;

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 org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.ArrayList;
import java.util.List;

import static com.intrasoft.ermis.transit.management.core.service.report.ReportServiceImpl.VALID_TAD_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@DisplayName("Report Service Implementation Unit Tests")
@ExtendWith(MockitoExtension.class)
class ReportServiceImplUT {

    @Mock
    private DossierDataService dossierDataService;

    @Mock
    private ErmisLogger ermisLogger;

    @InjectMocks
    private ReportServiceImpl reportService;

    @Test
    void testValidateOfficeRoleTypeWithValidType() {
        String validOfficeRoleType = "DEPARTURE";
        assertDoesNotThrow(() -> ReportServiceImpl.validateOfficeRoleType(validOfficeRoleType));

    }

    @Test
    void testValidateOfficeRoleTypeWithInvalidType() {
        String invalidOfficeRoleType = "InvalidType";

        Throwable exception = assertThrows(NullPointerException.class,
                () -> ReportServiceImpl.validateOfficeRoleType(invalidOfficeRoleType));

        assertEquals("Unknown OfficeRoleType: [InvalidType]. Cannot generate TAD document", exception.getMessage());
    }

    @Test
    void testValidateOfficeRoleTypeWithBlankType() {
        String blankOfficeRoleType = "";

        assertDoesNotThrow(() -> ReportServiceImpl.validateOfficeRoleType(blankOfficeRoleType));
    }

    @Test
    void testPrintReportMessageTypesForOfficeRoleType() {
        String resultDeparture = ReportServiceImpl.printReportMessageTypesForOfficeRoleType("DEPARTURE");
        assertEquals("[CC_029_C]", resultDeparture);
    }

    @Test
    void testPrintReportMessageTypesForNullOfficeRoleType() {
        assertThrows(NullPointerException.class,
                () -> ReportServiceImpl.printReportMessageTypesForOfficeRoleType(null));
    }

    @Test
    void testPrintReportMessageTypesForInvalidOfficeRoleType() {
        String invalidOfficeRoleType = "InvalidType";

        assertThrows(NullPointerException.class,
                () -> ReportServiceImpl.printReportMessageTypesForOfficeRoleType(invalidOfficeRoleType));
    }

    @Test
    void testGetMessageByMRNAndOfficeRoleType() {
        String mrn = "22DKHMEAOZJLUULEJ2";
        String officeRoleType = "DEPARTURE";

        when(dossierDataService.findByCriteria(mrn, null, officeRoleType, null, 2)).thenReturn(getListOfMovements());
        when(dossierDataService.getMessagesOverview(any(), any(), anyBoolean())).thenReturn(getListOfMessageOverviews());
        when(dossierDataService.getMessageById(any(), anyBoolean())).thenReturn(getMessage("CC_029_C"));

        Message message = reportService.getMessageByMRNAndOfficeRoleType(mrn, null, officeRoleType, VALID_TAD_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE);

        assertNotNull(message);

    }
    @Test
    void testGetMessageByMRNAndOfficeRoleType_caseOfCD003CPositive() {
        String mrn = "22DKHMEAOZJLUULEJ2";
        String officeRoleType = "DESTINATION";

        when(dossierDataService.findByCriteria(mrn, null, officeRoleType, null, 2)).thenReturn(getListOfMovements());
        when(dossierDataService.getMessagesOverview(any(), any(), anyBoolean())).thenReturn(getListOfMessageOverviews());
        doReturn(getPositiveCD003C("CD003C")).when(dossierDataService).getMessageById(any(), anyBoolean());

        Message message = reportService.getMessageByMRNAndOfficeRoleType(mrn, null, officeRoleType, VALID_TAD_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE);

        assertNotNull(message);

    }

    @Test
    void testGetMessageByMRNAndOfficeRoleType_caseOfCD003CNegative() {
        String mrn = "22DKHMEAOZJLUULEJ2";
        String officeRoleType = "DESTINATION";

        when(dossierDataService.findByCriteria(mrn, null, officeRoleType, null, 2)).thenReturn(getListOfMovements());
        when(dossierDataService.getMessagesOverview(any(), any(), anyBoolean())).thenReturn(getListOfMessageOverviews());
        doReturn(getNegativeCD003C("CD003C")).when(dossierDataService).getMessageById(any(), anyBoolean());

        Message message = reportService.getMessageByMRNAndOfficeRoleType(mrn, null, officeRoleType,VALID_TAD_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE);

        assertNull(message);

    }

    @Test
    void testGetMessageByMRNAndOfficeRoleType_returnsNull() {
        String mrn = "22DKHMEAOZJLUULEJ2";
        String officeRoleType = "DEPARTURE";

        when(dossierDataService.findByCriteria(any(), any(), any(), any(), anyInt())).thenReturn(getListOfMovements());
        when(dossierDataService.getMessagesOverview(any(), any(), anyBoolean())).thenReturn(getListOfMessageOverviews());
        when(dossierDataService.getMessageById(any(), anyBoolean())).thenReturn(null);

        Message message = reportService.getMessageByMRNAndOfficeRoleType(mrn, null, officeRoleType,VALID_TAD_REPORT_MESSAGE_TYPES_PER_OFFICE_ROLE_TYPE);

        assertNull(message);

    }

    private List<Movement> getListOfMovements() {
        List<Movement> movements = new ArrayList<>();

        Movement movement1 = Movement.builder()
                                     .id("movementId1")
                                     .officeRoleType("DEPARTURE")
                                     .officeId("OfficeId1")
                                     .mrn("22DKHMEAOZJLUULEJ2")
                                     .lrn("test")
                                     .build();
        movements.add(movement1);

        Movement movement2 = Movement.builder()
                                     .id("movementId2")
                                     .officeRoleType("DEPARTURE")
                                     .officeId("OfficeId2")
                                     .mrn("22DKHMEAOZJLUULEJ9")
                                     .lrn("test2")
                                     .build();
        movements.add(movement2);

        return movements;
    }

    private List<MessageOverview> getListOfMessageOverviews() {
        List<MessageOverview> messageOverviews = new ArrayList<>();

        MessageOverview overview1 = MessageOverview.builder()
                                                   .id("overviewId1")
                                                   .messageIdentification("messageId1")
                                                   .messageType("CC_029_C")
                                                   .build();
        messageOverviews.add(overview1);

        MessageOverview overview2 = MessageOverview.builder()
                                                   .id("overviewId2")
                                                   .messageIdentification("messageId2")
                                                   .messageType("CC_029_C")
                                                   .build();
        messageOverviews.add(overview2);

        return messageOverviews;
    }

    private Message getMessage(String messageType){
        Message message = Message.builder()
                                     .id("sampleMessageId")
                                     .messageType(messageType)
                                     .payload("<xml>...</xml>")
                                     .build();
        return message;
    }

    private Message getPositiveCD003C(String messageType){
        Message message = Message.builder()
                                     .id("sampleMessageId")
                                     .messageType(messageType)
                                     .payload("<CD003C xmlns:ns2=\"http://ncts.dgtaxud.ec\"><messageSender>NTA.DK</messageSender><messageRecipient>NTA.DK</messageRecipient><preparationDateAndTime>2022-09-29T02:24:13</preparationDateAndTime><messageIdentification>QIBYKV1GY89NFI</messageIdentification><messageType>CD003C</messageType><correlationIdentifier>68WSD33UTCLG0G</correlationIdentifier><TransitOperation><MRN>21DK86Z3AFTRPMWDL1</MRN><requestRejectionReasonCode></requestRejectionReasonCode></TransitOperation><CustomsOfficeOfDestinationActual><referenceNumber>DK005600</referenceNumber></CustomsOfficeOfDestinationActual></CD003C>")
                                     .build();
        return message;
    }
    private Message getNegativeCD003C(String messageType){
        Message message = Message.builder()
                                     .id("sampleMessageId")
                                     .messageType(messageType)
                                     .payload("<CD003C xmlns:ns2=\"http://ncts.dgtaxud.ec\"><messageSender>NTA.DK</messageSender><messageRecipient>NTA.DK</messageRecipient><preparationDateAndTime>2022-09-29T02:24:13</preparationDateAndTime><messageIdentification>QIBYKV1GY89NFI</messageIdentification><messageType>CD003C</messageType><correlationIdentifier>68WSD33UTCLG0G</correlationIdentifier><TransitOperation><MRN>21DK86Z3AFTRPMWDL1</MRN><requestRejectionReasonCode>1</requestRejectionReasonCode></TransitOperation><CustomsOfficeOfDestinationActual><referenceNumber>DK005600</referenceNumber></CustomsOfficeOfDestinationActual></CD003C>")
                                     .build();
        return message;
    }

}
