package com.braintree.actions.order;

import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.basecommerce.enums.FraudStatus;
import de.hybris.platform.core.enums.OrderStatus;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.fraud.FraudService;
import de.hybris.platform.fraud.impl.FraudServiceResponse;
import de.hybris.platform.fraud.impl.FraudSymptom;
import de.hybris.platform.fraud.model.FraudReportModel;
import de.hybris.platform.fraud.model.FraudSymptomScoringModel;
import de.hybris.platform.orderhistory.model.OrderHistoryEntryModel;
import de.hybris.platform.orderprocessing.model.OrderProcessModel;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.servicelayer.time.TimeService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Date;
import java.util.List;
import java.util.Set;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@UnitTest
@RunWith(MockitoJUnitRunner.class)
public class BrainTreeFraudCheckOrderInternalActionTest {

    private static final String PROVIDER_NAME = "providerName";
    private static final String SYMPTOM = "symptom";
    private static final String EXPLANATION = "explanation";
    private static final double SCORE = 2.0;

    @Mock
    private FraudService fraudService;
    @Mock
    private ModelService modelService;
    @Mock
    private TimeService timeService;

    @Spy
    @InjectMocks
    private BrainTreeFraudCheckOrderInternalAction unit;

    @Mock
    private OrderProcessModel process;
    @Mock
    private OrderModel order;
    @Mock
    private FraudServiceResponse fraudServiceResponse;
    @Mock
    private FraudReportModel fraudReportModel;
    @Mock
    private FraudSymptomScoringModel fraudSymptomScoringModel;
    @Mock
    private FraudSymptom fraudSymptom;
    @Mock
    private OrderHistoryEntryModel orderHistoryEntryModel;

    private Date date;

    @Before
    public void setUp() {
        date = new Date();

        doReturn(PROVIDER_NAME).when(unit).getProviderName();
        when(process.getOrder()).thenReturn(order);
        when(timeService.getCurrentTime()).thenReturn(date);
        when(fraudService.recognizeOrderSymptoms(PROVIDER_NAME, order)).thenReturn(fraudServiceResponse);
        when(fraudServiceResponse.getScore()).thenReturn(SCORE);
        when(modelService.create(FraudReportModel.class)).thenReturn(fraudReportModel);
        when(modelService.create(FraudSymptomScoringModel.class)).thenReturn(fraudSymptomScoringModel);
        when(fraudSymptom.getExplanation()).thenReturn(EXPLANATION);
        when(fraudSymptom.getSymptom()).thenReturn(SYMPTOM);
        when(fraudSymptom.getScore()).thenReturn(SCORE);
        when(modelService.create(OrderHistoryEntryModel.class)).thenReturn(orderHistoryEntryModel);
    }

    @Test
    public void shouldExecuteActionWhenScoreLessThanScoreLimit() {
        //given
        doReturn(10.0).when(unit).getScoreLimit();
        doReturn(12.0).when(unit).getScoreTolerance();
        when(order.getFraudReports()).thenReturn(Set.of(fraudReportModel));
        when(fraudServiceResponse.getSymptoms()).thenReturn(List.of(fraudSymptom));

        //when
        AbstractFraudCheckAction.Transition result = unit.executeAction(process);

        //then
        assertEquals(AbstractFraudCheckAction.Transition.OK, result);

        verify(fraudReportModel).setOrder(order);
        verify(fraudReportModel).setStatus(FraudStatus.OK);
        verify(fraudReportModel).setProvider(PROVIDER_NAME);
        verify(fraudReportModel).setTimestamp(date);
        verify(fraudReportModel).setCode(anyString());
        verify(fraudReportModel).setFraudSymptomScorings(List.of(fraudSymptomScoringModel));

        verify(fraudSymptomScoringModel).setScore(SCORE);
        verify(fraudSymptomScoringModel).setExplanation(EXPLANATION);
        verify(fraudSymptomScoringModel).setName(SYMPTOM);
        verify(fraudSymptomScoringModel).setFraudReport(fraudReportModel);

        verify(orderHistoryEntryModel).setDescription(anyString());
        verify(orderHistoryEntryModel).setOrder(order);
        verify(orderHistoryEntryModel).setTimestamp(date);

        verify(order).setStatus(OrderStatus.FRAUD_CHECKED);
        verify(order).setFraudulent(Boolean.FALSE);
        verify(order).setPotentiallyFraudulent(Boolean.FALSE);

        verify(modelService).save(order);
        verify(modelService).save(fraudReportModel);
        verify(modelService).save(orderHistoryEntryModel);
    }

    @Test
    public void shouldExecuteActionWhenScoreLessThanScoreLimitPlusScoreTolerance() {
        //given
        doReturn(1.0).when(unit).getScoreLimit();
        doReturn(12.0).when(unit).getScoreTolerance();
        when(order.getFraudReports()).thenReturn(Set.of(fraudReportModel));
        when(fraudServiceResponse.getSymptoms()).thenReturn(List.of(fraudSymptom));

        //when
        AbstractFraudCheckAction.Transition result = unit.executeAction(process);

        //then
        assertEquals(AbstractFraudCheckAction.Transition.POTENTIAL, result);

        verify(fraudReportModel).setOrder(order);
        verify(fraudReportModel).setStatus(FraudStatus.CHECK);
        verify(fraudReportModel).setProvider(PROVIDER_NAME);
        verify(fraudReportModel).setTimestamp(date);
        verify(fraudReportModel).setCode(anyString());
        verify(fraudReportModel).setFraudSymptomScorings(List.of(fraudSymptomScoringModel));

        verify(fraudSymptomScoringModel).setScore(SCORE);
        verify(fraudSymptomScoringModel).setExplanation(EXPLANATION);
        verify(fraudSymptomScoringModel).setName(SYMPTOM);
        verify(fraudSymptomScoringModel).setFraudReport(fraudReportModel);

        verify(orderHistoryEntryModel).setDescription(anyString());
        verify(orderHistoryEntryModel).setOrder(order);
        verify(orderHistoryEntryModel).setTimestamp(date);

        verify(order).setStatus(OrderStatus.FRAUD_CHECKED);
        verify(order).setFraudulent(Boolean.FALSE);
        verify(order).setPotentiallyFraudulent(Boolean.TRUE);

        verify(modelService).save(order);
        verify(modelService).save(fraudReportModel);
        verify(modelService).save(orderHistoryEntryModel);
    }

    @Test
    public void shouldExecuteActionWhenScoreHigherThanScoreLimitPlusScoreTolerance() {
        //given
        doReturn(1.0).when(unit).getScoreLimit();
        doReturn(0.0).when(unit).getScoreTolerance();

        //when
        AbstractFraudCheckAction.Transition result = unit.executeAction(process);

        //then
        assertEquals(AbstractFraudCheckAction.Transition.FRAUD, result);

        verify(fraudReportModel).setOrder(order);
        verify(fraudReportModel).setStatus(FraudStatus.FRAUD);
        verify(fraudReportModel).setProvider(PROVIDER_NAME);
        verify(fraudReportModel).setTimestamp(date);
        verify(fraudReportModel).setCode(anyString());

        verify(orderHistoryEntryModel).setDescription(anyString());
        verify(orderHistoryEntryModel).setOrder(order);
        verify(orderHistoryEntryModel).setTimestamp(date);

        verify(order).setStatus(OrderStatus.FRAUD_CHECKED);
        verify(order).setFraudulent(Boolean.TRUE);
        verify(order).setPotentiallyFraudulent(Boolean.FALSE);

        verify(modelService).save(order);
        verify(modelService).save(fraudReportModel);
        verify(modelService).save(orderHistoryEntryModel);
    }

}
