package com.braintree.transaction.service.impl;

import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.model.BrainTreeTransactionDetailModel;
import com.braintree.order.capture.partial.process.BraintreeOrderCompleteProcessService;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.core.enums.OrderStatus;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.orderprocessing.model.OrderProcessModel;
import de.hybris.platform.payment.dto.TransactionStatus;
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.processengine.BusinessProcessService;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.servicelayer.search.FlexibleSearchService;
import org.apache.commons.lang.StringUtils;
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.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;


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

    private static final String CODE = "11111111";
    private static final String ID = "12341234";
    private static final Double PRICE = 19.99;
    private static final String PAYMENT_PROVIDER = "PayPal";
    private static final String ORDER_PROCESS_PREFIX = "order-process";
    private static final String ORDER_PROCESS_SUFFIX = "_SubmitForSettlementEvent";

    private List<PaymentTransactionModel> paymentTransactionModels;
    private List<PaymentTransactionEntryModel> paymentTransactionEntryModels;

    @Mock
    private PaymentTransactionModel paymentTransactionModel;
    @Mock
    private PaymentTransactionEntryModel paymentTransactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel captureTransactionEntryModel;
    @Mock
    private OrderModel orderModel;
    @Mock
    private OrderProcessModel orderProcess;

    @Mock
    private FlexibleSearchService flexibleSearchService;
    @Mock
    private BusinessProcessService businessProcessService;
    @Mock
    private BraintreeOrderCompleteProcessService orderCompleteProcessService;
    @Mock
    private ModelService modelService;

    @Spy
    @InjectMocks
    private DefaultBrainTreePaymentTransactionService unit;

    @Before
    public void beforeEachTest() {
        paymentTransactionModels = new ArrayList<>();
        paymentTransactionModels.add(paymentTransactionModel);

        paymentTransactionEntryModels = new ArrayList<>();
        paymentTransactionEntryModels.add(paymentTransactionEntryModel);
    }

    @Test
    public void shouldGetTransactionsByRequestIdAndPaymentProvider() {
        when(flexibleSearchService.getModelsByExample(any(PaymentTransactionModel.class))).thenReturn(paymentTransactionModels);

        final List<PaymentTransactionModel> result = unit.getTransactionsByRequestIdAndPaymentProvider(ID, PAYMENT_PROVIDER);

        assertEquals(paymentTransactionModels, result);
        assertEquals(paymentTransactionModel, result.get(0));

        verify(flexibleSearchService).getModelsByExample(any(PaymentTransactionModel.class));
    }

    @Test
    public void shouldGetTransactionByRequestIdAndPaymentProvider() {
        doReturn(paymentTransactionModels).when(unit).getTransactionsByRequestIdAndPaymentProvider(ID, PAYMENT_PROVIDER);

        final PaymentTransactionModel result = unit.getTransactionByRequestIdAndPaymentProvider(ID, PAYMENT_PROVIDER);

        assertEquals(paymentTransactionModel, result);
        verify(unit).getTransactionsByRequestIdAndPaymentProvider(ID, PAYMENT_PROVIDER);
    }

    @Test
    public void shouldGetTransactionEntryByRequestId() {
        doReturn(paymentTransactionEntryModels).when(unit).getTransactionEntriesByRequestId(ID);

        final PaymentTransactionEntryModel result = unit.getTransactionEntryByRequestId(ID);

        assertEquals(paymentTransactionEntryModel, result);
        verify(unit).getTransactionEntriesByRequestId(ID);
    }

    @Test
    public void shouldGetTransactionEntriesByRequestId() {
        when(flexibleSearchService.getModelsByExample(any(PaymentTransactionEntryModel.class))).thenReturn(paymentTransactionEntryModels);

        final List<PaymentTransactionEntryModel> result = unit.getTransactionEntriesByRequestId(ID);

        assertEquals(paymentTransactionEntryModels, result);
        assertEquals(paymentTransactionEntryModel, result.get(0));

        verify(flexibleSearchService).getModelsByExample(any(PaymentTransactionEntryModel.class));
    }

    @Test
    public void shouldContinueSubmitOrder() {
        final BrainTreeTransactionDetailModel currentTransaction = mock(BrainTreeTransactionDetailModel.class);
        final BigDecimal amount = mock(BigDecimal.class);

        final Collection<OrderProcessModel> orderProcesses = new ArrayList<>();
        orderProcesses.add(orderProcess);

        when(currentTransaction.getId()).thenReturn(ID);
        doReturn(paymentTransactionModel).when(unit).getTransactionByRequestIdAndPaymentProvider(ID, "BrainTree");

        when(paymentTransactionModel.getOrder()).thenReturn(orderModel);

        when(paymentTransactionModel.getEntries()).thenReturn(paymentTransactionEntryModels);
        when(paymentTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        doNothing().when(modelService).save(paymentTransactionEntryModel);

        when(orderModel.getOrderProcess()).thenReturn(orderProcesses);
        when(orderProcess.getProcessDefinitionName()).thenReturn(ORDER_PROCESS_PREFIX);

        when(orderProcess.getCode()).thenReturn(CODE);

        doNothing().when(businessProcessService).triggerEvent(CODE + ORDER_PROCESS_SUFFIX);

        unit.continueSubmitOrder(currentTransaction, amount);

        verify(unit).getTransactionByRequestIdAndPaymentProvider(ID, "BrainTree");

        verify(paymentTransactionModel).getOrder();

        verify(paymentTransactionModel).getEntries();
        verify(paymentTransactionEntryModel).getType();
        verify(paymentTransactionEntryModel).setSubmittedForSettlementAmount(amount);

        verify(modelService).save(paymentTransactionEntryModel);

        verify(orderModel).getOrderProcess();
        verify(orderProcess).getProcessDefinitionName();

        verify(businessProcessService).triggerEvent(CODE + ORDER_PROCESS_SUFFIX);
    }

    @Test
    public void shouldContinueOrderProcess() {
        doNothing().when(orderCompleteProcessService).startOrderCompletionProcess(orderModel);

        unit.continueOrderProcess(orderModel);

        verify(orderCompleteProcessService).startOrderCompletionProcess(orderModel);
    }

    @Test
    public void shouldCheckIsValidTransactionId() {
        when(orderModel.getPaymentTransactions()).thenReturn(paymentTransactionModels);
        when(paymentTransactionModel.getEntries()).thenReturn(paymentTransactionEntryModels);

        when(paymentTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(paymentTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(paymentTransactionEntryModel.getRequestId()).thenReturn(ID);

        final boolean result = unit.isValidTransactionId(orderModel, ID);

        assertTrue(result);

        verify(orderModel).getPaymentTransactions();
        verify(paymentTransactionModel).getEntries();

        verify(paymentTransactionEntryModel).getType();
        verify(paymentTransactionEntryModel).getTransactionStatus();
        verify(paymentTransactionEntryModel).getRequestId();
    }

    @Test
    public void shouldCheckIsOrderFullyCaptured() {
        when(paymentTransactionEntryModel.getPaymentTransaction()).thenReturn(paymentTransactionModel);
        when(paymentTransactionEntryModel.getAmount()).thenReturn(new BigDecimal(PRICE));
        when(paymentTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(captureTransactionEntryModel.getAmount()).thenReturn(new BigDecimal(PRICE));
        when(captureTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.CAPTURE);
        when(captureTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());

        when(paymentTransactionModel.getEntries()).thenReturn(List.of(paymentTransactionEntryModel, captureTransactionEntryModel));


        final boolean result = unit.isOrderFullyCaptured(paymentTransactionEntryModel);

        assertTrue(result);

        verify(paymentTransactionModel).getEntries();
        verify(captureTransactionEntryModel).getType();
        verify(captureTransactionEntryModel).getAmount();
    }

    @Test
    public void shouldCheckIsOrderNotFullyCaptured() {
        when(paymentTransactionEntryModel.getPaymentTransaction()).thenReturn(paymentTransactionModel);
        when(paymentTransactionEntryModel.getAmount()).thenReturn(new BigDecimal(PRICE));
        when(paymentTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(paymentTransactionModel.getEntries()).thenReturn(List.of(paymentTransactionEntryModel));

        final boolean result = unit.isOrderFullyCaptured(paymentTransactionEntryModel);

        assertFalse(result);
    }

    @Test
    public void shouldCheckCanPerformDelayedCapture() throws BraintreeErrorException {
        when(paymentTransactionEntryModel.getPaymentTransaction()).thenReturn(paymentTransactionModel);
        when(paymentTransactionEntryModel.getAmount()).thenReturn(new BigDecimal(PRICE));
        when(paymentTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(paymentTransactionModel.getEntries()).thenReturn(List.of(paymentTransactionEntryModel));

        final boolean result = unit.canPerformDelayedCapture(paymentTransactionEntryModel, new BigDecimal(PRICE));

        assertTrue(result);

        verify(paymentTransactionModel).getEntries();
        verify(paymentTransactionEntryModel).getAmount();
    }

    @Test
    public void shouldSetOrderStatus() {
        final OrderStatus orderStatus = OrderStatus.APPROVED;

        unit.setOrderStatus(orderModel, orderStatus);

        verify(orderModel).setStatus(orderStatus);
        verify(modelService).save(orderModel);
        verify(modelService).refresh(orderModel);
    }

    @Test
    public void shouldResumeOrderProcess() {
        final Collection<OrderProcessModel> orderProcesses = new ArrayList<>();
        orderProcesses.add(orderProcess);

        when(orderModel.getOrderProcess()).thenReturn(orderProcesses);
        when(orderProcess.getProcessDefinitionName()).thenReturn(ORDER_PROCESS_PREFIX);

        when(orderProcess.getCode()).thenReturn(CODE);
        doNothing().when(businessProcessService).triggerEvent(CODE + ORDER_PROCESS_SUFFIX);

        unit.resumeOrderProcess(orderModel);

        verify(orderModel).getOrderProcess();
        verify(orderProcess).getProcessDefinitionName();

        verify(orderProcess).getCode();
        verify(businessProcessService).triggerEvent(CODE + ORDER_PROCESS_SUFFIX);
    }

    @Test
    public void shouldNotResumeOrderProcess() {
        final Collection<OrderProcessModel> orderProcesses = new ArrayList<>();
        orderProcesses.add(orderProcess);

        when(orderModel.getOrderProcess()).thenReturn(orderProcesses);
        when(orderProcess.getProcessDefinitionName()).thenReturn(StringUtils.EMPTY);

        unit.resumeOrderProcess(orderModel);

        verify(orderModel).getOrderProcess();
        verify(orderProcess).getProcessDefinitionName();
    }

    @Test
    public void shouldProcessPaymentCapturedOrder() {
        //when
        unit.processPaymentCapturedOrder(orderModel);

        //then
        verify(unit).processPaymentCapturedOrder(orderModel);
        verify(unit).continueOrderProcess(orderModel);
    }

}
