package com.braintree.order.refund;

import com.braintree.command.result.BrainTreeRefundTransactionResult;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.order.refund.impl.DefaultBraintreeRefundService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.core.model.c2l.CurrencyModel;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.payment.model.PaymentTransactionEntryModel;
import de.hybris.platform.payment.model.PaymentTransactionModel;
import de.hybris.platform.returns.model.ReturnRequestModel;
import de.hybris.platform.servicelayer.model.ModelService;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

import static org.mockito.Matchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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

    private static final Double ORIGIN_TOTAL_PRICE = 100d;
    private static final Double REFUND_TOTAL_PRICE = 90d;
    private static final int REFUND_CURRENCY_DIGITS = 12;
    private static final boolean REFUND_RESULT_SUCCESS = true;
    private static final boolean REFUND_RESULT_NOT_SUCCESS = false;

    @Mock
    private OrderModel refundOrder;
    @Mock
    private OrderModel originalOrder;
    @Mock
    private ReturnRequestModel returnRequestModel;
    @Mock
    private PaymentTransactionModel transactionModel;

    @Mock
    private ModelService modelService;

    @Mock
    private BrainTreePaymentService brainTreePaymentService;

    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;

    @InjectMocks
    private DefaultBraintreeRefundService unit;

    @Before
    public void setUp() {
        CurrencyModel currencyModel = Mockito.mock(CurrencyModel.class);
        when(currencyModel.getDigits()).thenReturn(REFUND_CURRENCY_DIGITS);

        List<PaymentTransactionModel> transactionModelList = new ArrayList<>();
        transactionModelList.add(transactionModel);

        PaymentTransactionEntryModel entryModel = Mockito.mock(PaymentTransactionEntryModel.class);
        List<PaymentTransactionEntryModel> entryModels = new ArrayList<>();
        entryModels.add(entryModel);

        when(transactionModel.getEntries()).thenReturn(entryModels);
        when(originalOrder.getPaymentTransactions()).thenReturn(transactionModelList);
        when(refundOrder.getPaymentTransactions()).thenReturn(transactionModelList);
        when(refundOrder.getCurrency()).thenReturn(currencyModel);
        when(refundOrder.getTotalPrice()).thenReturn(REFUND_TOTAL_PRICE);
        when(refundOrder.getOriginalVersion()).thenReturn(originalOrder);
        when(originalOrder.getTotalPrice()).thenReturn(ORIGIN_TOTAL_PRICE);
    }

    @Test
    public void applyBraintreeRefundShouldCreateRefundTransactionByTransactionService() {
        BrainTreeRefundTransactionResult refundTransactionResult = Mockito.mock(BrainTreeRefundTransactionResult.class);
        when(brainTreePaymentService.refundTransaction(any())).thenReturn(refundTransactionResult);
        when(refundTransactionResult.isSuccess()).thenReturn(REFUND_RESULT_SUCCESS);

        PaymentTransactionEntryModel entryModel = Mockito.mock(PaymentTransactionEntryModel.class);

        when(brainTreeTransactionService.createRefundTransaction(transactionModel, refundTransactionResult))
            .thenReturn(entryModel);
        when(modelService.clone(entryModel, PaymentTransactionEntryModel.class)).thenReturn(entryModel);

        unit.applyBraintreeRefund(refundOrder, originalOrder, returnRequestModel);
        brainTreeTransactionService.createRefundTransaction(transactionModel, refundTransactionResult);
        verify(modelService).saveAll(entryModel, transactionModel, originalOrder);
    }

    @Test(expected = AdapterException.class)
    public void applyBraintreeRefundShouldWThrowExceptionIfRefundTransactionNotSuccess() {
        BrainTreeRefundTransactionResult refundTransactionResult = Mockito.mock(BrainTreeRefundTransactionResult.class);
        when(brainTreePaymentService.refundTransaction(any())).thenReturn(refundTransactionResult);
        when(refundTransactionResult.isSuccess()).thenReturn(REFUND_RESULT_NOT_SUCCESS);

        unit.applyBraintreeRefund(refundOrder, originalOrder, returnRequestModel);
        brainTreeTransactionService.createRefundTransaction(transactionModel, refundTransactionResult);
    }

    @Test
    public void testApplyBraintreeRefundShouldCreateTransaction() {
        BrainTreeRefundTransactionResult refundTransactionResult = Mockito.mock(BrainTreeRefundTransactionResult.class);
        when(returnRequestModel.getOrder()).thenReturn(originalOrder);
        when(brainTreePaymentService.refundTransaction(any())).thenReturn(refundTransactionResult);
        when(refundTransactionResult.isSuccess()).thenReturn(REFUND_RESULT_SUCCESS);

        unit.applyBraintreeRefund(returnRequestModel, REFUND_TOTAL_PRICE);
        verify(brainTreeTransactionService).createRefundTransaction(transactionModel, refundTransactionResult);
    }

    @Test
    public void shouldSaveOriginalOrderWhenTotalPriceTheSame() {
        when(refundOrder.getTotalPrice()).thenReturn(ORIGIN_TOTAL_PRICE);
        when(originalOrder.getTotalPrice()).thenReturn(ORIGIN_TOTAL_PRICE);

        unit.applyBraintreeRefund(refundOrder, originalOrder, returnRequestModel);

        verify(modelService).refresh(originalOrder);
        verify(modelService).save(originalOrder);
    }

    @Test
    public void shouldSaveOriginalOrderWhenRefundOrderHaveNoTransaction() {

        List<PaymentTransactionModel> transactionModelList = new ArrayList<>();

        PaymentTransactionEntryModel entryModel = Mockito.mock(PaymentTransactionEntryModel.class);

        when(refundOrder.getPaymentTransactions()).thenReturn(transactionModelList);

        unit.applyBraintreeRefund(refundOrder, originalOrder, returnRequestModel);
        verify(modelService).save(originalOrder);
        verify(modelService).refresh(originalOrder);
        verify(modelService, never()).saveAll(entryModel, transactionModel, originalOrder);

    }
}
