package com.braintree.actions.order;

import com.braintree.enums.BrainTreePaymentMethod;
import com.braintree.model.BrainTreePaymentInfoModel;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.basecommerce.model.site.BaseSiteModel;
import de.hybris.platform.core.enums.OrderStatus;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.order.payment.CreditCardPaymentInfoModel;
import de.hybris.platform.orderprocessing.model.OrderProcessModel;
import de.hybris.platform.payment.PaymentService;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
import de.hybris.platform.payment.enums.PaymentTransactionType;
import de.hybris.platform.payment.model.PaymentTransactionEntryModel;
import de.hybris.platform.payment.model.PaymentTransactionModel;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.servicelayer.session.SessionExecutionBody;
import de.hybris.platform.servicelayer.session.SessionService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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

    private static final String CURRENT_SITE = "currentSite";
    private static final String UNDEFINED = "UNDEFINED";
    private static final String NOK = "NOK";
    private static final String OK = "OK";

    @Mock
    private PaymentService paymentService;
    @Mock
    private SessionService sessionService;
    @Mock
    private ModelService modelService;

    @Spy
    @InjectMocks
    private BrainTreeTakePaymentAction unit;

    @Mock
    private OrderProcessModel process;
    @Mock
    private OrderModel order;
    @Mock
    private PaymentTransactionModel paymentTransactionModel;
    @Mock
    private PaymentTransactionEntryModel paymentTransactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel authorizationTransactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel captureTransactionEntryModel;
    @Mock
    private BaseSiteModel baseSite;
    @Mock
    private BrainTreePaymentInfoModel paymentInfo;
    @Mock
    private CreditCardPaymentInfoModel creditCardPaymentInfo;

    @Captor
    private ArgumentCaptor<SessionExecutionBody> sessionExecutionBodyCaptor;
    @Captor
    private ArgumentCaptor<Map<String, Object>> parametersCaptor;

    @Before
    public void setUp() {
        when(process.getOrder()).thenReturn(order);
        when(order.getSite()).thenReturn(baseSite);
        when(order.getPaymentTransactions()).thenReturn(List.of(paymentTransactionModel));
        when(paymentTransactionModel.getEntries()).thenReturn(List.of(authorizationTransactionEntryModel));
        when(authorizationTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(authorizationTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(paymentService.capture(paymentTransactionModel)).thenReturn(paymentTransactionEntryModel);

        when(order.getPaymentInfo()).thenReturn(paymentInfo);
    }

    @Test
    public void shouldCaptureTransactionIfPayPalPaymentProvider() {
        //given
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(paymentInfo.getPaymentProvider()).thenReturn(BrainTreePaymentMethod.PAYPAL.getCode());

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        Map<String, Object> parameters = parametersCaptor.getValue();
        assertTrue(parameters.containsKey(CURRENT_SITE));
        assertEquals(baseSite, parameters.get(CURRENT_SITE));
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.OK, result);
        verify(modelService).save(order);
        verify(order).setStatus(OrderStatus.PAYMENT_CAPTURED);
    }

    @Test
    public void shouldCaptureTransactionIfPayPalIntentSale() {
        //given
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.OK, result);
        verify(modelService).save(order);
        verify(order).setStatus(OrderStatus.PAYMENT_CAPTURED);
    }

    @Test
    public void shouldCaptureTransactionIfOrderPaymentInfoInstanceOfBraintreePaymentInfoModel() {
        //given
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.OK, result);
        verify(modelService).save(order);
        verify(order).setStatus(OrderStatus.PAYMENT_CAPTURED);
    }

    @Test
    public void shouldCaptureTransactionIfOrderPaymentInfoInstanceOfCreditCardPaymentInfoModel() {
        //given
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(order.getPaymentInfo()).thenReturn(creditCardPaymentInfo);

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.OK, result);
        verify(modelService).save(order);
        verify(order).setStatus(OrderStatus.PAYMENT_CAPTURED);
    }

    @Test
    public void shouldReturnTransitionUndefinedIfCaptureIsNotPossible() {
        //given
        when(order.getPaymentInfo()).thenReturn(null);

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.UNDEFINED, result);
        verify(modelService, never()).save(any(OrderModel.class));
        verify(order, never()).setStatus(any(OrderStatus.class));
    }

    @Test
    public void shouldSetPartialCaptureStatusIfTransactionEntryInReviewStatus() {
        //given
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.REVIEW.name());

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.UNDEFINED, result);
        verify(modelService).save(order);
        verify(order).setStatus(OrderStatus.PARTIAL_CAPTURE);
    }

    @Test
    public void shouldReturnTransitionUndefinedIfTransactionEntryInRejectedStatus() {
        //given
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.REJECTED.name());

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.UNDEFINED, result);
        verify(modelService, never()).save(any(OrderModel.class));
        verify(order, never()).setStatus(any(OrderStatus.class));
    }

    @Test
    public void shouldSetPaymentNotCapturedStatusIfTransactionEntryInRejectedStatusAndBankDeclined() {
        //given
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.REJECTED.name());
        when(paymentTransactionEntryModel.getTransactionStatusDetails()).thenReturn(TransactionStatusDetails.BANK_DECLINE.name());

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.NOK, result);
        verify(modelService).save(order);
        verify(order).setStatus(OrderStatus.PAYMENT_NOT_CAPTURED);
    }

    @Test
    public void shouldSetTransitionOkIfOrderPaymentTransactionsAreEmpty() {
        //given
        when(order.getPaymentTransactions()).thenReturn(Collections.emptyList());

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.OK, result);
        verify(modelService, never()).save(any(OrderModel.class));
        verify(order, never()).setStatus(any(OrderStatus.class));
        verify(paymentService, never()).capture(any(PaymentTransactionModel.class));
    }

    @Test
    public void shouldGetTransition() {
        //when
        Set<String> transitions = unit.getTransitions();

        //then
        assertTrue(transitions.containsAll(List.of(OK, NOK, UNDEFINED)));
    }

    @Test
    public void shouldSkipCaptureIfItAlreadyCaptured() {
        //given
        when(paymentTransactionModel.getEntries()).thenReturn(List.of(captureTransactionEntryModel));
        when(captureTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(captureTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.CAPTURE);

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.OK, result);
        verify(paymentService, never()).capture(paymentTransactionModel);
    }

    @Test
    public void shouldSkipCaptureIfItContainRejectedAuthorization() {
        //given
        when(authorizationTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.REJECTED.name());

        //when
        unit.executeAction(process);

        //then
        verify(sessionService).executeInLocalViewWithParams(parametersCaptor.capture(), sessionExecutionBodyCaptor.capture());
        SessionExecutionBody sessionExecutionBody = sessionExecutionBodyCaptor.getValue();

        BrainTreeTakePaymentAction.Transition result = (BrainTreeTakePaymentAction.Transition) sessionExecutionBody.execute();

        assertEquals(BrainTreeTakePaymentAction.Transition.OK, result);
        verify(paymentService, never()).capture(paymentTransactionModel);
    }

}
