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.ddntav5152.messages.PostponeEnquiryEvent;
import com.intrasoft.ermis.transit.bpmnshared.BpmnManagementService;
import com.intrasoft.ermis.transit.common.enums.ProcessingStatusTypeEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Message;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Movement;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.BpmnFlowMessagesEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.BpmnFlowVariablesEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.BpmnTimersEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.ProcessDefinitionEnum;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.xml.bind.JAXBException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class CustomsOfficerActionServiceImpl implements CustomsOfficerActionService {

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

    private final ErmisLogger log;

    private final BpmnFlowService bpmnFlowService;

    private final BpmnManagementService bpmnManagementService;

    private final ProcessingStatusService processingStatusService;

    private final GuaranteeService guaranteeService;

    private final RecoveryService recoveryService;

    private final DossierDataService dossierDataService;

    private final PostReleaseTransitService postReleaseTransitService;

    private final AmendmentRequestService amendmentRequestService;

    private static final String FALLBACK_ACTIVITY_ID = "Gateway_Fallback";

    private static final String CANCELLED_ACTIVITY_ID = "Gateway_0rylnq0";

    @Override
    public void handleRecommendEnquiryAction(Movement movement) {
        if (ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED.getItemCode().equals(movement.getProcessingStatus())) {
            bpmnFlowService.correlateMessage(movement.getMrn(), BpmnFlowMessagesEnum.RECOMMEND_ENQUIRY_WITHOUT_006.getTypeValue());
        } else if (ProcessingStatusTypeEnum.OODEP_ARRIVED.getItemCode().equals(movement.getProcessingStatus())) {
            bpmnFlowService.correlateMessage(movement.getMrn(), BpmnFlowMessagesEnum.RECOMMEND_ENQUIRY_WITH_006.getTypeValue());
        }
    }

    @Override
    public void handlePostponeEnquiryAction(Movement movement, String messageId) throws JAXBException {
        Message postponeEnquiryMessage = dossierDataService.getMessageById(messageId);
        PostponeEnquiryEvent postponeEnquiryEvent = JSON_CONVERTER.convertStringToObject(postponeEnquiryMessage.getPayload(), PostponeEnquiryEvent.class);

        String formattedExpectedArrivalDate = Objects.nonNull(postponeEnquiryEvent.getExpectedArrivalDate()) ?
                "P" + ChronoUnit.DAYS.between(LocalDate.now(ZoneOffset.UTC), postponeEnquiryEvent.getExpectedArrivalDate()) + "D" :
                null;
        String formattedExpectedControlDate = Objects.nonNull(postponeEnquiryEvent.getExpectedControlDate()) ?
                "P" + ChronoUnit.DAYS.between(LocalDate.now(ZoneOffset.UTC), postponeEnquiryEvent.getExpectedControlDate()) + "D" :
                null;

        if (formattedExpectedArrivalDate != null) {
            amendmentRequestService.updateLimitDate(movement.getId(), postponeEnquiryEvent.getExpectedControlDate());

            processingStatusService.saveProcessingStatus(movement.getId(), ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED.getItemCode());
            log.info("Going to stop 'Time To Enquire Timer'");
            postReleaseTransitService.stopTimeToEnquireHolderTimerIfActive(movement.getMrn());

            boolean isTimerActive = bpmnManagementService.isTimerActive(
                    movement.getMrn(),
                    ProcessDefinitionEnum.OODEPARTURE_RELEASE_TRANSIT_PROCESS.getProcessDefinitionKey(),
                    BpmnTimersEnum.AWAIT_RECEIPT_CONTROL_RESULTS.getActivityId());

            if (isTimerActive) {
                bpmnManagementService.modifyProcessInstance(
                        movement.getMrn(),
                        ProcessDefinitionEnum.OODEPARTURE_RELEASE_TRANSIT_PROCESS.getProcessDefinitionKey(),
                        FALLBACK_ACTIVITY_ID,
                        Map.of(BpmnFlowVariablesEnum.ARRIVAL_ADVICE_TIMER.getTypeValue(), formattedExpectedArrivalDate,
                                BpmnFlowVariablesEnum.IS_CONTROL_TIMER_ACTIVE.getTypeValue(), true,
                                BpmnFlowVariablesEnum.IS_EXPECTED_ARRIVAL_DATE_EMPTY.getTypeValue(), false,
                                BpmnFlowVariablesEnum.HAS_ENQUIRE_TIMER_STARTED.getTypeValue(), false),
                        List.of(CANCELLED_ACTIVITY_ID)
                );

                bpmnManagementService.changedDueDateOfTimer(
                        movement.getMrn(),
                        ProcessDefinitionEnum.OODEPARTURE_RELEASE_TRANSIT_PROCESS.getProcessDefinitionKey(),
                        BpmnTimersEnum.AWAIT_RECEIPT_CONTROL_RESULTS.getActivityId(),
                        formattedExpectedControlDate);
            } else {
                bpmnManagementService.modifyProcessInstance(
                        movement.getMrn(),
                        ProcessDefinitionEnum.OODEPARTURE_RELEASE_TRANSIT_PROCESS.getProcessDefinitionKey(),
                        FALLBACK_ACTIVITY_ID,
                        Map.of(BpmnFlowVariablesEnum.ARRIVAL_ADVICE_TIMER.getTypeValue(), formattedExpectedArrivalDate,
                                BpmnFlowVariablesEnum.RECEIPT_CONTROL_RESULTS_TIMER.getTypeValue(), Objects.requireNonNull(formattedExpectedControlDate),
                                BpmnFlowVariablesEnum.IS_CONTROL_TIMER_ACTIVE.getTypeValue(), false,
                                BpmnFlowVariablesEnum.IS_EXPECTED_ARRIVAL_DATE_EMPTY.getTypeValue(), false,
                                BpmnFlowVariablesEnum.HAS_ENQUIRE_TIMER_STARTED.getTypeValue(), false),
                        List.of(CANCELLED_ACTIVITY_ID)
                );
            }
        } else {
            processingStatusService.saveProcessingStatus(movement.getId(), ProcessingStatusTypeEnum.OODEP_ARRIVED.getItemCode());
            log.info("Going to stop 'Time To Enquire Timer'");
            postReleaseTransitService.stopTimeToEnquireHolderTimerIfActive(movement.getMrn());

            bpmnManagementService.modifyProcessInstance(
                    movement.getMrn(),
                    ProcessDefinitionEnum.OODEPARTURE_RELEASE_TRANSIT_PROCESS.getProcessDefinitionKey(),
                    FALLBACK_ACTIVITY_ID,
                    Map.of(BpmnFlowVariablesEnum.RECEIPT_CONTROL_RESULTS_TIMER.getTypeValue(), Objects.requireNonNull(formattedExpectedControlDate),
                            BpmnFlowVariablesEnum.IS_EXPECTED_ARRIVAL_DATE_EMPTY.getTypeValue(), true,
                            BpmnFlowVariablesEnum.IS_CONTROL_TIMER_ACTIVE.getTypeValue(), false,
                            BpmnFlowVariablesEnum.HAS_ENQUIRE_TIMER_STARTED.getTypeValue(), false),
                    List.of(CANCELLED_ACTIVITY_ID)
            );
        }
    }

    @Override
    public void handleGuaranteeIntegrityCheckAction(Movement movement, String messageId) {
        guaranteeService.sendGuaranteeChecksToGMS(movement, messageId);
    }

    @Override
    public void handleRecommendRecoveryAction(Movement movement) {
        ProcessingStatusTypeEnum processingStatus = ProcessingStatusTypeEnum.findByItemCode(movement.getProcessingStatus());
        switch (processingStatus) {
            case OODEP_MOVEMENT_RELEASED:
                bpmnFlowService.correlateMessageWithValues(movement.getMrn(), BpmnFlowMessagesEnum.MANUALLY_RECOMMEND_RECOVERY.getTypeValue(), Map.of(
                        BpmnFlowVariablesEnum.IS_RECOVERY_PROCEDURE_STARTED_BY_OFFICER.getTypeValue(), true));
                break;
            default:
                recoveryService.startRecoveryForOfficerAction(movement.getId(), movement.getMrn());
                break;
        }
    }
}

package com.intrasoft.ermis.trs.officeofdeparture;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import com.intrasoft.ermis.transit.common.enums.ProcessingStatusTypeEnum;
import com.intrasoft.ermis.transit.officeofdeparture.domain.Movement;
import com.intrasoft.ermis.transit.officeofdeparture.domain.enums.BpmnFlowMessagesEnum;
import com.intrasoft.ermis.transit.officeofdeparture.service.BpmnFlowService;
import com.intrasoft.ermis.transit.officeofdeparture.service.CustomsOfficerActionServiceImpl;
import java.util.UUID;
import java.util.stream.Stream;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class CustomOfficerActionServiceUT {

    @Mock
    private BpmnFlowService bpmnFlowService;

    @InjectMocks
    private CustomsOfficerActionServiceImpl customOfficerActionService;

    @Captor
    private ArgumentCaptor<String> messageNameArgumentCaptor;

    @Captor
    private ArgumentCaptor<String> businessKeyArgumentCaptor;

    private static final String MRN = "TEST_MRN";

    @ParameterizedTest
    @MethodSource("namedArguments")
    public void testHandlingOfRecommendEnquiryAction(String processingStatus) {

        customOfficerActionService.handleRecommendEnquiryAction(createMovement(processingStatus));

        if (ProcessingStatusTypeEnum.OODEP_NONE.getItemCode().equals(processingStatus)) {
            verify(bpmnFlowService, times(0)).correlateMessage(any(), any());
        } else {
            verify(bpmnFlowService, times(1)).correlateMessage(businessKeyArgumentCaptor.capture(), messageNameArgumentCaptor.capture());
            assertEquals(MRN, businessKeyArgumentCaptor.getValue());
            if (ProcessingStatusTypeEnum.OODEP_ARRIVED.getItemCode().equals(processingStatus)) {
                assertEquals(BpmnFlowMessagesEnum.RECOMMEND_ENQUIRY_WITH_006.getTypeValue(), messageNameArgumentCaptor.getValue());
            } else if (ProcessingStatusTypeEnum.OODEP_MOVEMENT_RELEASED.getItemCode().equals(processingStatus)) {
                assertEquals(BpmnFlowMessagesEnum.RECOMMEND_ENQUIRY_WITHOUT_006.getTypeValue(), messageNameArgumentCaptor.getValue());
            }
        }
    }

    static Stream<Arguments> namedArguments() {
        return Stream.of(
                Arguments.of(Named.of(" Arrived", "A01")),
                Arguments.of(Named.of(" Movement Released", "A03")),
                Arguments.of(Named.of(" None (not valid processing status)", "A0"))
        );
    }

    private Movement createMovement(String processingStatus) {
        return Movement.builder()
                       .id(UUID.randomUUID().toString())
                       .processingStatus(processingStatus)
                       .mrn(MRN)
                       .build();
    }
}
