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

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.ddntav5152.messages.AcceptGuaranteeEvent;
import com.intrasoft.ermis.ddntav5152.messages.CC013CType;
import com.intrasoft.ermis.ddntav5152.messages.CC015CType;
import com.intrasoft.ermis.ddntav5152.messages.CC170CType;
import com.intrasoft.ermis.ddntav5152.messages.GuaranteeType01;
import com.intrasoft.ermis.ddntav5152.messages.GuaranteeType02;
import com.intrasoft.ermis.ddntav5152.messages.HouseConsignmentType06;
import com.intrasoft.ermis.ddntav5152.messages.MessageTypes;
import com.intrasoft.ermis.transit.common.enums.GuaranteeTypesEnum;
import com.intrasoft.ermis.transit.common.enums.MessageStatusEnum;
import com.intrasoft.ermis.transit.common.enums.ProcessingStatusTypeEnum;
import com.intrasoft.ermis.transit.common.exceptions.BaseException;
import com.intrasoft.ermis.transit.common.exceptions.NotFoundException;
import com.intrasoft.ermis.transit.common.mappers.MessageTypeMapper;
import com.intrasoft.ermis.transit.common.util.MessageUtil;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Declaration;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Message;
import com.intrasoft.ermis.transit.officeofdeparture.domain.MessageOverview;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Movement;
import com.intrasoft.ermis.transit.officeofdeparture.domain.WorkTask;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.BpmnFlowFallbackEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.BpmnFlowMessagesEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.ProcessDefinitionEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.events.ProcessInstanceCancellationResponseDomainEvent;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.JAXBException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class AmendmentRequestServiceImpl implements AmendmentRequestService {

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

    private static final String FALLBACK_CANCELLATION = "Cancelled by Main flow fallback";

    private final ErmisLogger log;

    private final DossierDataService dossierDataService;

    private final WorkTaskManagerDataService workTaskManagerDataService;

    private final GenerateMessageService generateMessageService;

    public static final String COMMON = "COMMON";

    private final MessageTypeMapper messageTypeMapper;

    private final MovementService movementService;

    private final BpmnFlowService bpmnFlowService;

    private static final List<String> guaranteeTypeList =
            Arrays.asList(GuaranteeTypesEnum.TYPE_0.getCode(), GuaranteeTypesEnum.TYPE_1.getCode(), GuaranteeTypesEnum.TYPE_2.getCode(),
                    GuaranteeTypesEnum.TYPE_4.getCode(), GuaranteeTypesEnum.TYPE_9.getCode());

    @Override
    public boolean checkIE204ConveyanceEligibility(String movementId) {
        // Guarantee types that provide IE204 conveyance eligibility:
        List<String> guaranteeTypesForIE204 = new ArrayList<>(guaranteeTypeList);

        // Retrieve CC015C message based on movementID:
        CC015CType cc015cMessage = getCC015CMessage(movementId);

        if (cc015cMessage != null) {
            // Check if Guarantee list from CC015C message contains at least one (1) guarantee of type
            // that deems IE204 eligibility as true, else false:
            return cc015cMessage.getGuarantee().stream().anyMatch(guarantee -> guaranteeTypesForIE204.contains(guarantee.getGuaranteeType()));
        } else {
            // Throw BaseException if CC015C with provided movementID was not found:
            throw new BaseException("ERROR in checkIE204ConveyanceEligibility(): CC015C Message was NULL!");
        }
    }

    @Override
    public ProcessingStatusTypeEnum checkPreviousState(String movementId) {
        return movementService.checkLatestState(movementId);
    }

    @Override
    public boolean checkCurrentState(String movementId, ProcessingStatusTypeEnum state) {
        return movementService.checkIfInGivenState(movementId, state);
    }

    @Override
    public boolean checkGMSGuarantees(String movementId) {

        return checkAmendedGuaranteeTypes(movementId);
    }

    @Override
    public boolean isAutomaticReleaseTimerActive(String movementId) {

        // TODO Implementation pending...

        return true;
    }

    @Override
    public void rejectMessage(String movementId, String messageId, boolean isIE015Rejected) throws JAXBException {
        generateMessageService.generateIE056(movementId, messageId, isIE015Rejected, CC013CType.class);
        dossierDataService.updateMessageStatus(messageId, MessageStatusEnum.REJECTED.name());
    }
    @Override
    public Declaration amendDeclarationAndCreateNewVersion(String movementId, String messageId, String messageType, boolean isFallbackDeclaration) throws JAXBException {
        Declaration declaration = dossierDataService.getLatestDeclarationByMovementId(movementId);

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

        CC015CType updatedCc015CTypeMessage = null;

        if (AcceptGuaranteeEvent.class.getSimpleName().equals(messageType) && !isFallbackDeclaration) {
            updatedCc015CTypeMessage = updateCc015CTypeWithAcceptGuaranteeEvent(movementId, cc015CTypeOfLatestDeclaration);
        } else if (MessageTypes.CC_013_C.value().equals(messageType)) {
            updatedCc015CTypeMessage = updateCc015CTypeWithCc013C(movementId, isFallbackDeclaration, cc015CTypeOfLatestDeclaration);
            updateMovementWithAmendment(movementId, messageId);
        } else if (MessageTypes.CC_170_C.value().equals(messageType) && !isFallbackDeclaration) {
            updatedCc015CTypeMessage = updateCc015CTypeWithCc170C(messageId, cc015CTypeOfLatestDeclaration);
        }

        if (updatedCc015CTypeMessage != null) {
            log.info("amendDeclarationAndCreateNewVersion(): Will create new Declaration on Movement: [{}] with received messageType: [{}]", movementId, messageType);
            String updatedCC015cXml = XmlConverter.marshal(updatedCc015CTypeMessage, NamespacesEnum.NTA.getValue(), MessageTypes.CC_015_C.value());
            Message updatedCC015CMessage = Message.builder()
                                                  .messageIdentification(cc015CTypeOfLatestDeclaration.getMessageIdentification())
                                                  .messageType(cc015CTypeOfLatestDeclaration.getMessageType().value())
                                                  .source(cc015CTypeOfLatestDeclaration.getMessageSender())
                                                  .destination(cc015CTypeOfLatestDeclaration.getMessageRecipient())
                                                  .domain(COMMON)
                                                  .payload(updatedCC015cXml)
                                                  .build();

            Declaration updatedDeclaration = Declaration.builder()
                                                        .movementId(movementId)
                                                        .payloadType(MessageTypes.CC_015_C.value())
                                                        .payload(updatedCC015CMessage.getPayload())
                                                        .version(declaration.getVersion() + 1)
                                                        .build();
            dossierDataService.createDeclaration(updatedDeclaration);
            return updatedDeclaration;
        }
        return null;
    }

    private CC015CType updateCc015CType(String movementId, boolean isFallbackDeclaration, CC015CType cc015CTypeOfLatestDeclaration,
            List<MessageOverview> cc013cMessageOverviewList) throws JAXBException {
        CC015CType updatedCc015CTypeMessage = null;
        if (!cc013cMessageOverviewList.isEmpty()) {
            Message cc013Message = dossierDataService.getMessageById(cc013cMessageOverviewList.get(0).getId());
            CC013CType cc013CTypeMessage = XmlConverter.buildMessageFromXML(cc013Message.getPayload(), CC013CType.class);
            updatedCc015CTypeMessage = createNewDeclarationBasedOnCC013(cc013CTypeMessage, cc015CTypeOfLatestDeclaration);
            if(isFallbackDeclaration) {
                updatedCc015CTypeMessage.getExtensions().addAll(cc015CTypeOfLatestDeclaration.getExtensions());
            }
        } else {
            log.error("No message with type CC013C found for movementId: {}", movementId);
        }
        return updatedCc015CTypeMessage;
    }

    @Override
    public void fallBackToPrelodgedRiskStep(String movementId) {
        // Find any OPEN work tasks in database and change their status before process modification.
        cancelAllOpenWorkTasks(movementId);
        // Since the action happens in the initialization flow, movementId == processBusinessKey.
        modifyProcessInstance(
                movementId,
                ProcessDefinitionEnum.OODEPARTURE_INITIALIZATION_PROCESS.getProcessDefinitionKey(),
                BpmnFlowFallbackEnum.INITIALIZATION_PROCESS_GATEWAY_PRELODGED_RISK.getActivityId(),
                List.of(BpmnFlowFallbackEnum.EVENT_GATEWAY_GPR.getActivityId())
        );
    }

    @Override
    public void initiateFallbackToMainFlow(String movementId) {
        // Find any OPEN work tasks in database and change their status before process modification.
        cancelAllOpenWorkTasks(movementId);
        // Cancel Control process, if started - initiated via Kafka, before modifying the Main flow.
        // Since the action happens in the control flow, movement.getMRN == processBusinessKey.
        cancelProcessInstanceIfExists(
                getMRN(movementId),
                com.intrasoft.ermis.transit.contracts.control.enums.ProcessDefinitionEnum.CTL_MAIN_FLOW.getProcessDefinitionKey(),
                FALLBACK_CANCELLATION
        );
    }

    @Override
    public void continueFallbackToMainFlow(ProcessInstanceCancellationResponseDomainEvent responseDomainEvent) {
        // Called by ProcessInstanceCancellationResponseHandler when the fallback initiation is completed.
        // (Since the action happens in the main flow, movement.getMRN == processBusinessKey.)
        if (responseDomainEvent.isCompleted()) {
            modifyProcessInstance(
                    responseDomainEvent.getProcessBusinessKey(),
                    ProcessDefinitionEnum.OODEPARTURE_MAIN_PROCESS.getProcessDefinitionKey(),
                    BpmnFlowFallbackEnum.MAIN_PROCESS_RISK_CONTROL.getActivityId(),
                    List.of()
            );
        } else {

            log.warn("Process Instance cancellation was not completed for Business Key: {}!", responseDomainEvent.getProcessBusinessKey());
        }
    }

    @Override
    public void fallBackToStartOfGuaranteeProcess(String movementId) {
        // Since the action happens in the 'Register Guarantee Use' flow, movement.getMRN == processBusinessKey.
        modifyProcessInstance(
                getMRN(movementId),
                ProcessDefinitionEnum.OODEPARTURE_REGISTER_GUARANTEE_USE_PROCESS.getProcessDefinitionKey(),
                BpmnFlowFallbackEnum.REGISTER_GUARANTEE_PROCESS_INITIALIZE_REGISTER_GUARANTEE_USE.getActivityId(),
                List.of()
        );
    }

    @Override
    public void triggerGuaranteeFlowContinuation(String movementId) {
        // Since the action happens in the 'Register Guarantee Use' flow, movement.getMRN == processBusinessKey.
        String processBusinessKey = getMRN(movementId);

        bpmnFlowService.correlateMessage(processBusinessKey, BpmnFlowMessagesEnum.GUARANTEE_AMENDMENT_RECEIVED_MESSAGE.getTypeValue());
    }

    @Override
    public void updateLimitDate(String movementId, LocalDate expectedArrivalDate) throws JAXBException {
        Declaration declaration = dossierDataService.getLatestDeclarationByMovementId(movementId);
        CC015CType cc015CType = XmlConverter.buildMessageFromXML(declaration.getPayload(), CC015CType.class);
        cc015CType.getTransitOperation().setLimitDate(expectedArrivalDate);
        String updatedCC015cXml = XmlConverter.marshal(cc015CType, NamespacesEnum.NTA.getValue(), MessageTypes.CC_015_C.value());

        Declaration updatedDeclaration = Declaration.builder()
                                                    .movementId(movementId)
                                                    .payloadType(MessageTypes.CC_015_C.value())
                                                    .payload(updatedCC015cXml)
                                                    .version(declaration.getVersion() + 1)
                                                    .build();
        dossierDataService.createDeclaration(updatedDeclaration);
    }

    private CC015CType getCC015CMessage(String movementId) {

        CC015CType cc015cTypeMessage;

        try {
            // Fetch Declaration:
            Declaration declaration = dossierDataService.getLatestDeclarationByMovementId(movementId);
            // Retrieve CC015C message:
            cc015cTypeMessage = XmlConverter.buildMessageFromXML(declaration.getPayload(), CC015CType.class);
            return cc015cTypeMessage;
        } catch (Exception e) {

            throw new BaseException("ERROR in getCC015CMessage()", e);
        }
    }

    private CC015CType getPreviousVersionCC015CMessage(String movementId) {

        CC015CType cc015cTypeMessage;

        try {
            // Fetch latest (current) Declaration:
            Declaration currentDeclaration = dossierDataService.getLatestDeclarationByMovementId(movementId);
            // Retrieve its version:
            Integer currentVersion = currentDeclaration.getVersion();
            // Fetch previous Declaration:
            Declaration previousDeclaration = dossierDataService.getDeclarationByMovementIdAndVersion(movementId, currentVersion - 1);
            // Retrieve CC015C message:
            cc015cTypeMessage = XmlConverter.buildMessageFromXML(previousDeclaration.getPayload(), CC015CType.class);

            return cc015cTypeMessage;
        } catch (Exception e) {

            throw new BaseException("ERROR in getPreviousVersionCC015CMessage()", e);
        }
    }

    private CC013CType getCC013CMessage(String movementId) {

        CC013CType cc013cTypeMessage;

        try {

            List<MessageOverview> cc013cMessageOverviewList = dossierDataService.getMessagesOverview(movementId, MessageTypes.CC_013_C.value());

            // Retrieve CC013C message:
            Message cc013cMessage;
            if (!cc013cMessageOverviewList.isEmpty()) {
                cc013cMessage = dossierDataService.getMessageById(cc013cMessageOverviewList.get(0).getId());
            } else {
                throw new NotFoundException("getCC013CMessage() : Returned EMPTY MessageOverview List for movementId: " + movementId);
            }

            cc013cTypeMessage = XmlConverter.buildMessageFromXML(cc013cMessage.getPayload(), CC013CType.class);
            return cc013cTypeMessage;
        } catch (Exception e) {

            throw new BaseException(String.format("ERROR in getCC013CMessage() for movementId: %s", movementId), e);
        }
    }

    private String getMRN(String movementId) {

        Movement movement;

        try {
            // Retrieve movement:
            movement = dossierDataService.getMovementById(movementId);

            return movement.getMrn();
        } catch (Exception e) {

            throw new BaseException("ERROR in getMRN()", e);
        }
    }

    private boolean checkAmendedGuaranteeTypes(String movementId) {

        // Retrieve previous IE015 & current IE013 messages based on movementID:
        CC015CType cc015cMessage = getPreviousVersionCC015CMessage(movementId);
        CC013CType cc013cMessage = getCC013CMessage(movementId);

        if (cc013cMessage != null) {
            // Check if there were amendments on them:
            return amendmentOnSpecificGuaranteeTypes(cc015cMessage, cc013cMessage);
        } else {
            // Throw BaseException if CC013C with provided movementID was not found:
            throw new BaseException("ERROR in checkGuaranteeEligibility(): for movementId: [" + movementId + "]. CC013C Message was NULL!");
        }
    }

    private boolean amendmentOnSpecificGuaranteeTypes(CC015CType cc015cMessage, CC013CType cc013cMessage) {

        List<GuaranteeType01> cc013cSpecificGuarantees =
                cc013cMessage.getGuarantee()
                             .stream()
                             .filter(guaranteeType01 -> guaranteeTypeList.contains(guaranteeType01.getGuaranteeType()))
                             .toList();

        List<GuaranteeType01> cc015cSpecificGuarantees =
                guaranteeType02ListToType01List(cc015cMessage.getGuarantee()
                                                             .stream()
                                                             .filter(guaranteeType02 -> guaranteeTypeList.contains(guaranteeType02.getGuaranteeType()))
                                                             .toList());

        // If NOT identical, then there was an amendment on guarantee of specific type.
        // Covers cases of added, removed or changed guarantees (013 > 015 | 015 > 013  | 013 != 015).
        return !(cc013cSpecificGuarantees.containsAll(cc015cSpecificGuarantees) && cc015cSpecificGuarantees.containsAll(cc013cSpecificGuarantees));
    }

    private List<GuaranteeType01> guaranteeType02ListToType01List(List<GuaranteeType02> guaranteeType02List) {

        List<GuaranteeType01> guaranteeType01List = new ArrayList<>();

        guaranteeType02List.forEach(guaranteeType02 -> {
            GuaranteeType01 guaranteeType01 = new GuaranteeType01();
            guaranteeType01.setSequenceNumber(guaranteeType02.getSequenceNumber());
            guaranteeType01.setGuaranteeType(guaranteeType02.getGuaranteeType());
            guaranteeType01.setOtherGuaranteeReference(guaranteeType02.getOtherGuaranteeReference());
            guaranteeType01.getGuaranteeReference().addAll(guaranteeType02.getGuaranteeReference());

            guaranteeType01List.add(guaranteeType01);
        });

        return guaranteeType01List;
    }

    private void modifyProcessInstance(String processBusinessKey, String processDefinitionKey, String activityId, List<String> nonCancelableActivityIds) {

        bpmnFlowService.modifyProcessInstance(processBusinessKey, processDefinitionKey, activityId, nonCancelableActivityIds);
    }

    private void cancelProcessInstanceIfExists(String processBusinessKey, String processDefinitionKey, String cancellationReason) {

        bpmnFlowService.cancelProcessInstanceIfExists(processBusinessKey, processDefinitionKey, cancellationReason);
    }

    private void cancelAllOpenWorkTasks(String movementId) {

        // Find any OPEN work tasks in database and change their status to 'CANCELLED'.
        List<WorkTask> openWorkTaskList =
                new ArrayList<>(workTaskManagerDataService.findOpenWorkTasksByMovementId(movementId));

        openWorkTaskList.forEach(workTask -> workTaskManagerDataService.autoUpdateWorkTask(workTask.getWorkTaskId()));
    }

    private CC015CType createNewDeclarationBasedOnCC013(CC013CType cc013CTypeMessage, CC015CType cc015CTypeOfLatestDeclaration) {
        CC015CType updatedCC015C;

        if (cc013CTypeMessage.getTransitOperation().getAmendmentTypeFlag().equals("1")) {
            CC015CType cc015cUpdatedGuarantee = messageTypeMapper.fromCC013toCC015CTypeGuranteeAmend(cc013CTypeMessage);
            updatedCC015C = cc015CTypeOfLatestDeclaration;
            updatedCC015C.getGuarantee().clear();
            updatedCC015C.getGuarantee().addAll(cc015cUpdatedGuarantee.getGuarantee());
        } else {
            updatedCC015C = messageTypeMapper.fromCC013toCC015CType(cc013CTypeMessage);
            updatedFieldsOfCC015CMessage(cc015CTypeOfLatestDeclaration, updatedCC015C);
        }
        updatedCC015C.setPreparationDateAndTime(LocalDateTime.now());
        return updatedCC015C;
    }

    @SneakyThrows
    private CC015CType createNewDeclarationBasedOnCC170(CC170CType cc170cTypeMessage, CC015CType cc015CTypeOfLatestDeclaration) {

        //Representative amendment
        cc015CTypeOfLatestDeclaration.setRepresentative(cc170cTypeMessage.getRepresentative());

        //Consignment level amendments
        cc015CTypeOfLatestDeclaration.getConsignment().setInlandModeOfTransport(cc170cTypeMessage.getConsignment().getInlandModeOfTransport());
        cc015CTypeOfLatestDeclaration.getConsignment().setContainerIndicator(cc170cTypeMessage.getConsignment().getContainerIndicator());
        cc015CTypeOfLatestDeclaration.getConsignment().setModeOfTransportAtTheBorder(cc170cTypeMessage.getConsignment().getModeOfTransportAtTheBorder());

        cc015CTypeOfLatestDeclaration.getConsignment().setPlaceOfLoading(cc170cTypeMessage.getConsignment().getPlaceOfLoading());

        cc015CTypeOfLatestDeclaration.getConsignment().setLocationOfGoods(
                messageTypeMapper.fromLocationOfGoodsType03ToType05(cc170cTypeMessage.getConsignment().getLocationOfGoods()));

        cc015CTypeOfLatestDeclaration.getConsignment().getDepartureTransportMeans().clear();
        cc015CTypeOfLatestDeclaration.getConsignment()
                         .getDepartureTransportMeans()
                         .addAll(messageTypeMapper.fromDepartureTransportMeansType05ToType03List(
                                 cc170cTypeMessage.getConsignment().getDepartureTransportMeans()));


        cc015CTypeOfLatestDeclaration.getConsignment().getActiveBorderTransportMeans().clear();
        cc015CTypeOfLatestDeclaration.getConsignment()
                         .getActiveBorderTransportMeans()
                         .addAll(messageTypeMapper.fromActiveBorderTransportMeansType03ToType02List(
                                 cc170cTypeMessage.getConsignment().getActiveBorderTransportMeans()));

        cc015CTypeOfLatestDeclaration.getConsignment().getTransportEquipment().clear();
        cc015CTypeOfLatestDeclaration.getConsignment().getTransportEquipment().addAll(cc170cTypeMessage.getConsignment().getTransportEquipment());

        //HouseConsignment level amendments
        cc015CTypeOfLatestDeclaration.getConsignment().getHouseConsignment().forEach(cc015cHouseConsignment -> {

            HouseConsignmentType06 matchedHouseConsignment = cc170cTypeMessage.getConsignment().getHouseConsignment().stream().filter(cc170cHouseConsignment ->
                                                                                      cc015cHouseConsignment.getSequenceNumber().equals(cc170cHouseConsignment.getSequenceNumber()))
                                                                              .findFirst()
                                                                              .orElse(null);
            if (matchedHouseConsignment != null) {
                cc015cHouseConsignment.getDepartureTransportMeans().clear();
                cc015cHouseConsignment.getDepartureTransportMeans().addAll(matchedHouseConsignment.getDepartureTransportMeans());
            }
        });

        return cc015CTypeOfLatestDeclaration;
    }



    private CC015CType amendDeclarationWithAcceptGuaranteeEvent(AcceptGuaranteeEvent acceptGuaranteeEvent, CC015CType cc015CTypeMessageOfLastDeclaration) {
        cc015CTypeMessageOfLastDeclaration.getGuarantee().clear();
        cc015CTypeMessageOfLastDeclaration.getGuarantee().addAll(acceptGuaranteeEvent.getGuarantee());
        return cc015CTypeMessageOfLastDeclaration;
    }

    private void updatedFieldsOfCC015CMessage(CC015CType cc015CTypeOfLatestDeclaration, CC015CType updatedCC015C) {
        updatedCC015C.setMessageRecipient(cc015CTypeOfLatestDeclaration.getMessageRecipient());
        updatedCC015C.setMessageSender(cc015CTypeOfLatestDeclaration.getMessageSender());
        updatedCC015C.setPreparationDateAndTime(cc015CTypeOfLatestDeclaration.getPreparationDateAndTime());
        updatedCC015C.setMessageIdentification(cc015CTypeOfLatestDeclaration.getMessageIdentification());
        updatedCC015C.setMessageType(cc015CTypeOfLatestDeclaration.getMessageType());
        updatedCC015C.setCorrelationIdentifier(cc015CTypeOfLatestDeclaration.getCorrelationIdentifier());
        // PSD-3285. If the CC013C does not have an <LRN> element, then copy the LRN from the latest Declaration that should ALWAYS have a value
        if(StringUtils.isBlank(updatedCC015C.getTransitOperation().getLRN())) {
            log.info("updatedFieldsOfCC015CMessage()-1 : Will set the LRN of the newly created Declaration as the LRN of the latest one: [{}]", cc015CTypeOfLatestDeclaration.getTransitOperation().getLRN());
            updatedCC015C.getTransitOperation().setLRN(cc015CTypeOfLatestDeclaration.getTransitOperation().getLRN());
        } else  {
            log.info("updatedFieldsOfCC015CMessage()-2 : Will update the LRN of the newly created Declaration from the one of the new Message: [{}]", updatedCC015C.getTransitOperation().getLRN());
        }

    }

    private CC015CType updateCc015CTypeWithAcceptGuaranteeEvent(String movementId, CC015CType cc015CTypeOfLatestDeclaration) {
        List<MessageOverview> acceptGuaranteeMessageOverviewList =
                dossierDataService.getMessagesOverview(movementId, AcceptGuaranteeEvent.class.getSimpleName());

        if (acceptGuaranteeMessageOverviewList.isEmpty()) {
            log.error("No message with type AcceptGuaranteeEvent found for movementId: {}", movementId);
            return null;
        }
        Message acceptGuaranteeMessage = dossierDataService.getMessageById(acceptGuaranteeMessageOverviewList.get(0).getId());
        AcceptGuaranteeEvent acceptGuaranteeEvent = JSON_CONVERTER.convertStringToObject(acceptGuaranteeMessage.getPayload(), AcceptGuaranteeEvent.class);
        return amendDeclarationWithAcceptGuaranteeEvent(acceptGuaranteeEvent, cc015CTypeOfLatestDeclaration);
    }

    private CC015CType updateCc015CTypeWithCc013C(String movementId, boolean isFallbackDeclaration, CC015CType cc015CTypeOfLatestDeclaration)
            throws JAXBException {
        List<MessageOverview> cc013cMessageOverviewList = dossierDataService.getMessagesOverview(movementId, MessageTypes.CC_013_C.value());
        return updateCc015CType(movementId, isFallbackDeclaration, cc015CTypeOfLatestDeclaration, cc013cMessageOverviewList);
    }

    private CC015CType updateCc015CTypeWithCc170C(String ie170MessageId, CC015CType cc015CTypeOfLatestDeclaration) throws JAXBException {
        Message cc170Message = dossierDataService.getMessageById(ie170MessageId);
        CC170CType cc170CTypeMessage = XmlConverter.buildMessageFromXML(cc170Message.getPayload(), CC170CType.class);
        return createNewDeclarationBasedOnCC170(cc170CTypeMessage, cc015CTypeOfLatestDeclaration);
    }

    private void updateMovementWithAmendment(String movementId, String messageId) throws JAXBException {
        Message cc013Message = dossierDataService.getMessageById(messageId);
        CC013CType cc013Payload = XmlConverter.buildMessageFromXML(cc013Message.getPayload(), CC013CType.class);
        String customsOfficeOfDestinationDeclared = cc013Payload.getCustomsOfficeOfDestinationDeclared().getReferenceNumber();
        String countryOfDestination = MessageUtil.extractOfficeCountryCode(customsOfficeOfDestinationDeclared);

        Movement movement = dossierDataService.getMovementById(movementId);
        movement.setCountryOfDestination(countryOfDestination);
        movement.setOfficeOfDestination(customsOfficeOfDestinationDeclared);

        dossierDataService.updateMovement(movement);
    }
}

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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.intrasoft.ermis.common.xml.parsing.XmlConverter;
import com.intrasoft.ermis.ddntav5152.messages.CC013CType;
import com.intrasoft.ermis.ddntav5152.messages.CC015CType;
import com.intrasoft.ermis.ddntav5152.messages.MessageTypes;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Declaration;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Message;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Movement;
import com.intrasoft.ermis.transit.officeofdeparture.service.AmendmentRequestServiceImpl;
import com.intrasoft.ermis.trs.officeofdeparture.util.GeneratorHelper;
import java.util.List;
import javax.xml.bind.JAXBException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class AmendmentRequestServiceUT extends GeneratorHelper {

    @InjectMocks
    private AmendmentRequestServiceImpl amendmentRequestService;

    private static Movement createMovement(String countryOfDestination, String customsOfficeOfDestinationDeclared) {
        Movement expectedMovement = new Movement();
        expectedMovement.setCountryOfDestination(countryOfDestination);
        expectedMovement.setOfficeOfDestination(customsOfficeOfDestinationDeclared);
        return expectedMovement;
    }

    @Test
    void test_amendment_with_ie013_having_NotNull_LRN() {
        try {
            CC015CType cc015CType = XmlConverter.buildMessageFromXML(loadTextFile("CC015C_valid"), CC015CType.class);
            CC013CType cc013Type = XmlConverter.buildMessageFromXML(loadTextFile("CC013C"), CC013CType.class);
            Message cc013cMessage = createCC013CMessage(cc013Type);
            Declaration existingDeclaration = createDeclaration(cc015CType);

            when(dossierDataService.getLatestDeclarationByMovementId(MOVEMENT_ID)).thenReturn(existingDeclaration);
            when(dossierDataService.getMessagesOverview(MOVEMENT_ID, MessageTypes.CC_013_C.value())).thenReturn(List.of(cc013cMessage));
            when(dossierDataService.getMessageById(anyString())).thenReturn(cc013cMessage);
            when(dossierDataService.getMovementById(anyString())).thenReturn(new Movement());

            Declaration newDeclaration =
                    amendmentRequestService.amendDeclarationAndCreateNewVersion(MOVEMENT_ID, MESSAGE_ID, MessageTypes.CC_013_C.value(), false);

            CC015CType cc015CTypeOfDeclarationCreated = XmlConverter.buildMessageFromXML(newDeclaration.getPayload(), CC015CType.class);
            assertEquals(cc015CTypeOfDeclarationCreated.getTransitOperation().getLRN(), cc013Type.getTransitOperation().getLRN());
            assertEquals(newDeclaration.getVersion(), existingDeclaration.getVersion() + 1);
            verify(dossierDataService).updateMovement(createMovement(cc013Type.getConsignment().getCountryOfDestination(),
                    cc013Type.getCustomsOfficeOfDestinationDeclared().getReferenceNumber()));
        } catch (JAXBException e) {
            fail();
        }
    }

    @Test
    void test_amendment_with_ie013_having_Null_LRN() {

        try {
            CC015CType cc015CType = XmlConverter.buildMessageFromXML(loadTextFile("CC015C_valid"), CC015CType.class);
            CC013CType cc013Type = XmlConverter.buildMessageFromXML(loadTextFile("CC013C"), CC013CType.class);
            cc013Type.getTransitOperation().setLRN(null);
            Message cc013cMessage = createCC013CMessage(cc013Type);
            Declaration existingDeclaration = createDeclaration(cc015CType);

            when(dossierDataService.getLatestDeclarationByMovementId(MOVEMENT_ID)).thenReturn(existingDeclaration);
            when(dossierDataService.getMessagesOverview(MOVEMENT_ID, MessageTypes.CC_013_C.value())).thenReturn(List.of(cc013cMessage));
            when(dossierDataService.getMessageById(anyString())).thenReturn(cc013cMessage);
            when(dossierDataService.getMovementById(anyString())).thenReturn(new Movement());

            Declaration newDeclaration =
                    amendmentRequestService.amendDeclarationAndCreateNewVersion(MOVEMENT_ID, MESSAGE_ID, MessageTypes.CC_013_C.value(), false);

            CC015CType cc015CTypeOfDeclarationCreated = XmlConverter.buildMessageFromXML(newDeclaration.getPayload(), CC015CType.class);
            assertEquals(cc015CTypeOfDeclarationCreated.getTransitOperation().getLRN(), cc015CType.getTransitOperation().getLRN());
            assertEquals(newDeclaration.getVersion(), existingDeclaration.getVersion() + 1);
            verify(dossierDataService).updateMovement(createMovement(cc013Type.getConsignment().getCountryOfDestination(),
                    cc013Type.getCustomsOfficeOfDestinationDeclared().getReferenceNumber()));
        } catch (JAXBException e) {
            fail();
        }
    }

    private Message createCC013CMessage(CC013CType cc013CType) {
        return Message.builder()
                      .id(MESSAGE_ID)
                      .messageType(MessageTypes.CC_013_C.value())
                      .payload(XmlConverter.marshal(cc013CType))
                      .build();
    }

}
