package com.braintree.order.refund.partial.services.impl;

import com.braintree.command.request.BrainTreeRefundTransactionRequest;
import com.braintree.command.result.BrainTreeRefundTransactionResult;
import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.user.UserModel;
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.user.UserService;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.InjectMocks;
import org.mockito.runners.MockitoJUnitRunner;

import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.times;
import static org.junit.Assert.assertTrue;

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

    private static final String REQUEST_ID = "REQUEST_ID";
    private static final String BRAINTREE_ORDER_ID = "BRAINTREE_ORDER_ID";
    private static final String USER_UID = "testUserUid";
    private static final String BODY = "{fieldName:exampleValue}";
    private static final String ERROR_BODY = "{errors: ['First error', 'Second Error']}";

    @Mock
    private OrderModel orderModel;
    @Mock
    private PaymentTransactionModel transactionModel;
    @Mock
    private PaymentTransactionEntryModel entryModel;
    @Mock
    private BrainTreeRefundTransactionResult refundTransactionResult;
    @Mock
    private BigDecimal amount;
    @Mock
    private UserModel userModel;

    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;
    @Mock
    private BrainTreePaymentService brainTreePaymentService;
    @Mock
    private UserService userService;

    @Spy
    @InjectMocks
    private DefaultBraintreePartialRefundServiceImpl unit;

    @Test
    public void shouldCreatePartialRefundTransaction() throws BraintreeErrorException {
        doReturn(true).when(unit).eligibleForPartialRefund(entryModel);
        when(entryModel.getRequestId()).thenReturn(REQUEST_ID);
        doReturn(true).when(unit).isValidTransactionId(orderModel, REQUEST_ID);
        when(orderModel.getBrainTreeOrderId()).thenReturn(BRAINTREE_ORDER_ID);
        doReturn(mock(PaymentTransactionEntryModel.class)).when(unit).createPartialRefundTransaction(entryModel, amount, BRAINTREE_ORDER_ID);

        unit.partialRefundTransaction(orderModel, entryModel, amount);

        verify(unit).eligibleForPartialRefund(entryModel);
        verify(entryModel).getRequestId();
        verify(unit).isValidTransactionId(orderModel, REQUEST_ID);
        verify(orderModel).getBrainTreeOrderId();
        verify(unit).createPartialRefundTransaction(entryModel, amount, BRAINTREE_ORDER_ID);
    }

    @Test(expected = Exception.class)
    public void shouldThrowFirstBraintreeErrorException() throws BraintreeErrorException {
        doReturn(true).when(unit).eligibleForPartialRefund(entryModel);
        when(entryModel.getRequestId()).thenReturn(REQUEST_ID);
        doReturn(false).when(unit).isValidTransactionId(orderModel, REQUEST_ID);

        unit.partialRefundTransaction(orderModel, entryModel, amount);
    }

    @Test(expected = Exception.class)
    public void shouldThrowSecondBraintreeErrorException() throws BraintreeErrorException {
        doReturn(false).when(unit).eligibleForPartialRefund(entryModel);

        unit.partialRefundTransaction(orderModel, entryModel, amount);
    }

    @Test
    public void shouldReturnTrueFotTypeCapture() {
        when(entryModel.getType()).thenReturn(PaymentTransactionType.CAPTURE);
        when(entryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(entryModel.getTransactionStatusDetails()).thenReturn(TransactionStatusDetails.SUCCESFULL.name());

        final boolean result = unit.eligibleForPartialRefund(entryModel);

        assertTrue(result);
        verify(entryModel).getType();
        verify(entryModel).getTransactionStatus();
        verify(entryModel).getTransactionStatusDetails();
    }

    @Test
    public void shouldReturnTrueFotTypePartialCapture() {
        when(entryModel.getType()).thenReturn(PaymentTransactionType.PARTIAL_CAPTURE);
        when(entryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(entryModel.getTransactionStatusDetails()).thenReturn(TransactionStatusDetails.SUCCESFULL.name());

        final boolean result = unit.eligibleForPartialRefund(entryModel);

        assertTrue(result);
        verify(entryModel, times(2)).getType();
        verify(entryModel).getTransactionStatus();
        verify(entryModel).getTransactionStatusDetails();
    }

    @Test
    public void shouldCreatePartialRefundTransactionProtected() throws BraintreeErrorException {
        when(userService.getCurrentUser()).thenReturn(userModel);
        when(userModel.getUid()).thenReturn(USER_UID);
        when(entryModel.getRequestId()).thenReturn(REQUEST_ID);
        when(brainTreePaymentService.refundTransaction(any(BrainTreeRefundTransactionRequest.class))).thenReturn(refundTransactionResult);
        when(refundTransactionResult.getResponseBody()).thenReturn(BODY);
        when(brainTreeTransactionService.createPartialRefundTransaction(entryModel, refundTransactionResult, amount)).thenReturn(mock(PaymentTransactionEntryModel.class));

        unit.createPartialRefundTransaction(entryModel, amount, BRAINTREE_ORDER_ID);

        verify(userService).getCurrentUser();
        verify(userModel).getUid();
        verify(entryModel).getRequestId();
        verify(brainTreePaymentService).refundTransaction(any(BrainTreeRefundTransactionRequest.class));
        verify(refundTransactionResult).getResponseBody();
        verify(brainTreeTransactionService).createPartialRefundTransaction(entryModel, refundTransactionResult, amount);
    }

    @Test(expected = Exception.class)
    public void shouldThrowBraintreeErrorException() throws BraintreeErrorException {
        when(userService.getCurrentUser()).thenReturn(userModel);
        when(userModel.getUid()).thenReturn(null);

        unit.createPartialRefundTransaction(entryModel, amount, BRAINTREE_ORDER_ID);
    }

    @Test(expected = Exception.class)
    public void shouldThrowExceptionFromValidateTransactionResponse() throws BraintreeErrorException {
        when(userService.getCurrentUser()).thenReturn(userModel);
        when(userModel.getUid()).thenReturn(USER_UID);
        when(entryModel.getRequestId()).thenReturn(REQUEST_ID);
        when(brainTreePaymentService.refundTransaction(any(BrainTreeRefundTransactionRequest.class))).thenReturn(refundTransactionResult);
        when(refundTransactionResult.getResponseBody()).thenReturn(ERROR_BODY);

        unit.createPartialRefundTransaction(entryModel, amount, BRAINTREE_ORDER_ID);
    }

    @Test
    public void shouldReturnTrueForTypeCapture() {
        final List<PaymentTransactionModel> transactionModels = Arrays.asList(transactionModel);
        final List<PaymentTransactionEntryModel> entryModels = Arrays.asList(entryModel);

        when(orderModel.getPaymentTransactions()).thenReturn(transactionModels);
        when(transactionModel.getEntries()).thenReturn(entryModels);
        when(entryModel.getType()).thenReturn(PaymentTransactionType.CAPTURE);
        when(entryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(entryModel.getRequestId()).thenReturn(REQUEST_ID);

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

        assertTrue(result);
        verify(orderModel).getPaymentTransactions();
        verify(transactionModel).getEntries();
        verify(entryModel).getType();
        verify(entryModel).getTransactionStatus();
        verify(entryModel).getRequestId();
    }

    @Test
    public void shouldReturnTrueForTypePartialCapture() {
        final List<PaymentTransactionModel> transactionModels = Arrays.asList(transactionModel);
        final List<PaymentTransactionEntryModel> entryModels = Arrays.asList(entryModel);

        when(orderModel.getPaymentTransactions()).thenReturn(transactionModels);
        when(transactionModel.getEntries()).thenReturn(entryModels);
        when(entryModel.getType()).thenReturn(PaymentTransactionType.PARTIAL_CAPTURE);
        when(entryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(entryModel.getRequestId()).thenReturn(REQUEST_ID);

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

        assertTrue(result);
        verify(orderModel).getPaymentTransactions();
        verify(transactionModel).getEntries();
        verify(entryModel, times(2)).getType();
        verify(entryModel).getTransactionStatus();
        verify(entryModel).getRequestId();
    }

}
