package com.braintree.order.capture.partial.services;

import com.braintree.command.result.BrainTreeSaleTransactionResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.order.capture.partial.services.impl.DefaultBraintreePartialCaptureService;
import com.braintree.order.capture.partial.strategy.BraintreePartialOrderCalculationStrategy;
import com.braintree.transaction.service.BrainTreePaymentTransactionService;
import com.braintree.transaction.service.BrainTreeTransactionService;
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.core.model.user.AddressModel;
import de.hybris.platform.core.model.user.CustomerModel;
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.servicelayer.model.ModelService;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.math.BigDecimal;
import java.util.function.Predicate;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
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 DefaultBraintreePartialCaptureServiceTest {

    private static final String FIRST_NAME = "firstName";
    private static final String LAST_NAME = "lastName";
    private static final String EMAIL = "email";
    private static final String POSTAL_CODE = "postalCode";
    private static final String LINE_1 = "line1";
    private static final String LINE_2 = "line2";

    private static final String TRANSACTION_ID = "testId";

    @Mock
    private AddressModel addressModel;
    @Mock
    private PaymentTransactionModel paymentTransactionModel;
    @Mock
    private PaymentTransactionEntryModel txModel;
    @Mock
    private OrderModel orderModel;
    @Mock
    private CustomerModel userModel;
    @Mock
    private BrainTreePaymentInfoModel paymentInfoModel;
    @Mock
    private PaymentTransactionEntryModel entryModel;
    @Mock
    private BrainTreeConfigService brainTreeConfigService;

    @Mock
    private BrainTreePaymentTransactionService brainTreePaymentTransactionService;

    @Mock
    private BraintreePartialOrderCalculationStrategy partialOrderCalculationStrategy;

    @Mock
    private ModelService modelService;

    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;
    @Mock
    private BrainTreeSaleTransactionResult result;

    @Mock
    private BrainTreePaymentService brainTreePaymentService;

    @InjectMocks
    private DefaultBraintreePartialCaptureService unit;

    private final BigDecimal bigDecimal = BigDecimal.TEN;

    @Before
    public void setUp() {
        when(addressModel.getFirstname()).thenReturn(FIRST_NAME);
        when(addressModel.getLastname()).thenReturn(LAST_NAME);
        when(addressModel.getEmail()).thenReturn(EMAIL);
        when(addressModel.getPostalcode()).thenReturn(POSTAL_CODE);
        when(addressModel.getLine1()).thenReturn(LINE_1);
        when(addressModel.getLine2()).thenReturn(LINE_2);

        when(brainTreePaymentService.partialCaptureTransaction(any())).thenReturn(result);
    }

    @Test
    public void shouldPartialCaptureWithUncompletedOrder() {
        when(entryModel.getPaymentTransaction()).thenReturn(paymentTransactionModel);
        when(orderModel.getUser()).thenReturn(userModel);
        when(orderModel.getPaymentAddress()).thenReturn(addressModel);
        when(orderModel.getDeliveryAddress()).thenReturn(addressModel);
        when(orderModel.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(result.isSuccess()).thenReturn(true);
        when(brainTreeTransactionService.createPaymentTransaction(any(), any(), any(), any(), any())).thenReturn(entryModel);

        unit.partialCapture(orderModel, bigDecimal, txModel, false);
        verify(brainTreePaymentTransactionService).setOrderStatus(orderModel, OrderStatus.PARTIAL_CAPTURE);
    }

    @Test
    public void shouldPartialCaptureWithCompletedOrder() {
        when(entryModel.getPaymentTransaction()).thenReturn(paymentTransactionModel);
        when(orderModel.getUser()).thenReturn(userModel);
        when(orderModel.getPaymentAddress()).thenReturn(addressModel);
        when(orderModel.getDeliveryAddress()).thenReturn(addressModel);
        when(orderModel.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(brainTreeTransactionService.createPaymentTransaction(any(), any(), any(), any(), any())).thenReturn(entryModel);
        when(brainTreePaymentTransactionService.isOrderFullyCaptured(txModel)).thenReturn(true);
        when(result.isSuccess()).thenReturn(true);

        unit.partialCapture(orderModel, bigDecimal, txModel, false);

        verify(brainTreePaymentTransactionService).processPaymentCapturedOrder(orderModel);
    }

    @Test
    public void shouldGetAuthorizeTransactionTypePredicateWhenTransactionAcceptedAndTypeEqualsAuthorization() {
        Predicate<PaymentTransactionEntryModel> predicate = unit.getAuthorizeTransactionTypePredicate();
        when(txModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(txModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.toString());
        assertTrue(predicate.test(txModel));
    }

    @Test
    public void shouldGetCaptureTransactionTypePredicateWhenTransactionAcceptedAndTypeEqualsCapture() {
        Predicate<PaymentTransactionEntryModel> predicate = unit.getCaptureTransactionTypePredicate();
        when(txModel.getType()).thenReturn(PaymentTransactionType.CAPTURE);
        when(txModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.toString());
        assertTrue(predicate.test(txModel));
    }

    @Test
    public void shouldGetCaptureTransactionTypePredicateWhenTransactionAcceptedAndTypeEqualsPartialCapture() {
        Predicate<PaymentTransactionEntryModel> predicate = unit.getCaptureTransactionTypePredicate();
        when(txModel.getType()).thenReturn(PaymentTransactionType.PARTIAL_CAPTURE);
        when(txModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.toString());
        assertTrue(predicate.test(txModel));
    }

    @Test
    public void shouldGetRefundTransactionTypePredicateWhenTransactionAcceptedAndTypeEqualsRefundPartial() {
        Predicate<PaymentTransactionEntryModel> predicate = unit.getRefundTransactionTypePredicate();
        when(txModel.getType()).thenReturn(PaymentTransactionType.REFUND_PARTIAL);
        when(txModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.toString());
        assertTrue(predicate.test(txModel));
    }


    @Test
    public void shouldGetTransactionAmountWhenPossibleCaptureLessThanNotCaptured() {
        when(entryModel.getAmount()).thenReturn(BigDecimal.TEN);
        when(entryModel.getRequestId()).thenReturn(TRANSACTION_ID);
        when(partialOrderCalculationStrategy.calculateTransaction(any(), any())).thenReturn(BigDecimal.ONE);
        Assert.assertEquals(BigDecimal.ONE, unit.getTransactionAmount(entryModel, orderModel));
    }

    @Test
    public void isPartialCapturePossibleShouldCallConfigService() {
        unit.isPartialCapturePossible(orderModel);
        verify(brainTreeConfigService).isMultiCaptureEnabled();
    }

    @Test
    public void shouldPartialCaptureWithCompletedOrderWhenFinishOrderIsTrueAndNotFullyCaptured() {
        when(entryModel.getPaymentTransaction()).thenReturn(paymentTransactionModel);
        when(orderModel.getUser()).thenReturn(userModel);
        when(orderModel.getPaymentAddress()).thenReturn(addressModel);
        when(orderModel.getDeliveryAddress()).thenReturn(addressModel);
        when(orderModel.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(brainTreeTransactionService.createPaymentTransaction(any(), any(), any(), any(), any())).thenReturn(entryModel);
        when(brainTreePaymentTransactionService.isOrderFullyCaptured(txModel)).thenReturn(false);
        when(result.isSuccess()).thenReturn(true);

        unit.partialCapture(orderModel, bigDecimal, txModel, true);

        verify(brainTreePaymentTransactionService).processPaymentCapturedOrder(orderModel);
    }

    @Test
    public void shouldDoNoPartialCaptureWithUnSuccessfulOrder() {
        when(orderModel.getUser()).thenReturn(userModel);
        when(orderModel.getPaymentAddress()).thenReturn(addressModel);
        when(orderModel.getDeliveryAddress()).thenReturn(addressModel);
        when(orderModel.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(brainTreeTransactionService.createPaymentTransaction(any(), any(), any(), any(), any())).thenReturn(entryModel);
        when(result.isSuccess()).thenReturn(false);

        unit.partialCapture(orderModel, bigDecimal, txModel, false);

        verify(brainTreePaymentTransactionService, never()).processPaymentCapturedOrder(orderModel);
    }

    @Test
    public void shouldGetPossibleAmountForCapture(){
        when(partialOrderCalculationStrategy.calculateCaptureAmount(orderModel)).thenReturn(new BigDecimal(100));

        BigDecimal result = unit.getPossibleAmountForCapture(orderModel);

        verify(modelService).refresh(orderModel);
        assertEquals(new BigDecimal(100), result);
    }
}
