package com.braintree.facade.backoffice.impl;

import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.order.capture.partial.services.BraintreePartialCaptureService;
import com.braintree.order.submitForSettlement.service.BraintreeSubmitForSettlementService;
import com.braintree.transaction.service.BrainTreePaymentTransactionService;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.ordermanagementfacades.payment.data.PaymentTransactionEntryData;
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.dto.converter.Converter;
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.List;
import java.util.function.Predicate;

import static de.hybris.platform.core.enums.OrderStatus.CANCELLED;
import static de.hybris.platform.core.enums.OrderStatus.COMPLETED;
import static de.hybris.platform.core.enums.OrderStatus.PAYMENT_AUTHORIZED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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

    private static final String TRANSACTION_ID_1 = "transaction1";
    private static final String TRANSACTION_ID_2 = "transaction2";
    private static final BigDecimal AMOUNT = BigDecimal.TEN;

    @Mock
    private BraintreePartialCaptureService braintreePartialCaptureService;
    @Mock
    private BraintreeSubmitForSettlementService braintreeSubmitForSettlementService;
    @Mock
    private Converter<PaymentTransactionEntryModel, PaymentTransactionEntryData> ordermanagementPaymentTransactionEntryConverter;
    @Mock
    private BrainTreePaymentTransactionService brainTreePaymentTransactionService;

    @Mock
    private OrderModel order;
    @Mock
    private PaymentTransactionModel transactionModel;
    @Mock
    private PaymentTransactionEntryModel transactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel authorizationTransactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel captureTransactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel rejectedAuthorizationTransactionEntryModel;
    @Mock
    private Predicate<PaymentTransactionEntryModel> transactionPredicate;

    @Spy
    @InjectMocks
    private DefaultBraintreeBackofficeMultiCaptureFacade unit;

    @Before
    public void setUp() {
        when(braintreePartialCaptureService.getPossibleAmountForCapture(order)).thenReturn(BigDecimal.ONE);
    }

    @Test
    public void shouldReturnFalseIfOrderNull() {
        assertFalse(unit.isMultiCapturePossible(null));
    }

    @Test
    public void shouldReturnFalseWhenOrderStatusIsCompleted() {
        when(order.getStatus()).thenReturn(COMPLETED);

        boolean result = unit.isMultiCapturePossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenOrderStatusIsCanceled() {
        when(order.getStatus()).thenReturn(CANCELLED);

        boolean result = unit.isMultiCapturePossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenNoAuthorizationTransactionPresent() {
        when(order.getStatus()).thenReturn(PAYMENT_AUTHORIZED);
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));

        when(transactionModel.getEntries()).thenReturn(List.of(transactionEntryModel));

        when(braintreePartialCaptureService.getAuthorizeTransactionTypePredicate()).thenReturn(transactionPredicate);
        when(transactionPredicate.test(transactionEntryModel)).thenReturn(false);

        boolean result = unit.isMultiCapturePossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenOrderIsFullyCaptured() {
        when(order.getStatus()).thenReturn(PAYMENT_AUTHORIZED);
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));

        when(transactionModel.getEntries()).thenReturn(List.of(transactionEntryModel));


        when(braintreePartialCaptureService.getAuthorizeTransactionTypePredicate()).thenReturn(transactionPredicate);
        when(transactionPredicate.test(transactionEntryModel)).thenReturn(true);
        when(brainTreePaymentTransactionService.isOrderFullyCaptured(transactionEntryModel)).thenReturn(true);

        boolean result = unit.isMultiCapturePossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnTrueWhenOrderInAcceptedStatusAndNotFullyCaptured() {
        when(order.getStatus()).thenReturn(PAYMENT_AUTHORIZED);
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));

        when(transactionModel.getEntries()).thenReturn(List.of(transactionEntryModel));


        when(braintreePartialCaptureService.getAuthorizeTransactionTypePredicate()).thenReturn(transactionPredicate);
        when(transactionPredicate.test(transactionEntryModel)).thenReturn(true);
        when(brainTreePaymentTransactionService.isOrderFullyCaptured(transactionEntryModel)).thenReturn(false);

        boolean result = unit.isMultiCapturePossible(order);

        assertTrue(result);
    }

    @Test
    public void shouldReturnAcceptedAuthorizationTransactionEntry() {
        when(transactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(transactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());
        when(captureTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.CAPTURE);
        when(rejectedAuthorizationTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(rejectedAuthorizationTransactionEntryModel.getTransactionStatus()).thenReturn(TransactionStatus.REJECTED.name());

        when(transactionModel.getEntries()).thenReturn(List.of(transactionEntryModel, rejectedAuthorizationTransactionEntryModel, captureTransactionEntryModel));
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));

        List<PaymentTransactionEntryModel> result = unit.getMultiCaptureableTransactions(order);

        assertEquals(1, result.size());
        assertEquals(transactionEntryModel, result.get(0));
    }

    @Test
    public void shouldReturnAuthorizationAmountByTransactionId() {
        when(transactionEntryModel.getRequestId()).thenReturn(TRANSACTION_ID_1);
        when(transactionEntryModel.getAmount()).thenReturn(AMOUNT);

        when(authorizationTransactionEntryModel.getRequestId()).thenReturn(TRANSACTION_ID_2);

        doReturn(List.of(transactionEntryModel, authorizationTransactionEntryModel)).when(unit).getMultiCaptureableTransactions(order);

        BigDecimal result = unit
                .getAuthorizationAmountByTransactionId(order, TRANSACTION_ID_1);

        assertEquals(AMOUNT, result);
    }

    @Test
    public void shouldReturnZeroAuthorizationAmountIfNoTransactionWithIdPresent() {
        when(authorizationTransactionEntryModel.getRequestId()).thenReturn(TRANSACTION_ID_2);

        doReturn(List.of(authorizationTransactionEntryModel)).when(unit).getMultiCaptureableTransactions(order);

        BigDecimal result = unit
                .getAuthorizationAmountByTransactionId(order, TRANSACTION_ID_1);

        assertEquals(BigDecimal.ZERO, result);
    }

    @Test
    public void shouldPerformPartialCapture() throws BraintreeErrorException {
        PaymentTransactionEntryData data = new PaymentTransactionEntryData();
        when(braintreePartialCaptureService.partialCapture(order, AMOUNT, transactionEntryModel, false)).thenReturn(captureTransactionEntryModel);
        when(ordermanagementPaymentTransactionEntryConverter.convert(captureTransactionEntryModel)).thenReturn(data);

        PaymentTransactionEntryData result = unit.partialCapture(order, AMOUNT, transactionEntryModel, false);

        assertEquals(data, result);
        verify(braintreePartialCaptureService).partialCapture(order, AMOUNT, transactionEntryModel, false);
        verify(ordermanagementPaymentTransactionEntryConverter).convert(captureTransactionEntryModel);
    }

    @Test(expected = BraintreeErrorException.class)
    public void shouldThrowExceptionDuringPartialCapture() throws BraintreeErrorException {
        PaymentTransactionEntryData data = new PaymentTransactionEntryData();
        when(braintreePartialCaptureService.partialCapture(order, AMOUNT, transactionEntryModel, false)).thenThrow(new BraintreeErrorException("Error during partial capture"));

        unit.partialCapture(order, AMOUNT, transactionEntryModel, false);

        verify(braintreePartialCaptureService).partialCapture(order, AMOUNT, transactionEntryModel, false);
    }

    @Test
    public void shouldReturnPossibleAmountForCapture() {
        when(braintreePartialCaptureService.getPossibleAmountForCapture(order)).thenReturn(AMOUNT);

        BigDecimal result = unit.getPossibleAmountForCapture(order);

        assertEquals(AMOUNT, result);
    }

    @Test
    public void shouldReturnTrueIfPartialCapturePossible() {
        when(braintreePartialCaptureService.isPartialCapturePossible(order)).thenReturn(true);

        boolean result = unit.isPartialCapturePossible(order);

        assertTrue(result);
    }

    @Test
    public void shouldReturnFalseIfPartialCaptureNotPossible() {
        when(braintreePartialCaptureService.isPartialCapturePossible(order)).thenReturn(false);

        boolean result = unit.isPartialCapturePossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnTransactionAmount() {
        when(braintreePartialCaptureService.getTransactionAmount(transactionEntryModel, order)).thenReturn(AMOUNT);

        BigDecimal result = unit.getTransactionAmount(transactionEntryModel, order);

        assertEquals(AMOUNT, result);
    }

    @Test
    public void shouldPerformSubmitForSettlement() throws BraintreeErrorException {
        PaymentTransactionEntryData data = new PaymentTransactionEntryData();
        when(braintreeSubmitForSettlementService.submitForSettlement(order, AMOUNT, transactionEntryModel, false)).thenReturn(captureTransactionEntryModel);
        when(ordermanagementPaymentTransactionEntryConverter.convert(captureTransactionEntryModel)).thenReturn(data);

        PaymentTransactionEntryData result = unit.submitForSettlement(order, AMOUNT, transactionEntryModel, false);

        assertEquals(data, result);
        verify(braintreeSubmitForSettlementService).submitForSettlement(order, AMOUNT, transactionEntryModel, false);
        verify(ordermanagementPaymentTransactionEntryConverter).convert(captureTransactionEntryModel);
    }

    @Test(expected = BraintreeErrorException.class)
    public void shouldThrowExceptionDuringSubmitForSettlement() throws BraintreeErrorException {
        PaymentTransactionEntryData data = new PaymentTransactionEntryData();
        when(braintreeSubmitForSettlementService.submitForSettlement(order, AMOUNT, transactionEntryModel, false)).thenThrow(new BraintreeErrorException("Error during partial capture"));

        unit.submitForSettlement(order, AMOUNT, transactionEntryModel, false);

        verify(braintreeSubmitForSettlementService).submitForSettlement(order, AMOUNT, transactionEntryModel, false);
    }

    @Test
    public void shouldReturnTrueIfSubmitForSettlementPossible() {
        when(braintreeSubmitForSettlementService.isSubmitForSettlementAvailable(order)).thenReturn(true);

        boolean result = unit.isSubmitForSettlementAvailable(order);

        assertTrue(result);
    }

    @Test
    public void shouldReturnFalseIfSubmitForSettlementNotPossible() {
        when(braintreeSubmitForSettlementService.isSubmitForSettlementAvailable(order)).thenReturn(false);

        boolean result = unit.isSubmitForSettlementAvailable(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnCaptureTransactionTypePredicate() {
        when(braintreePartialCaptureService.getCaptureTransactionTypePredicate()).thenReturn(transactionPredicate);

        Predicate<PaymentTransactionEntryModel> result = unit.getCaptureTransactionTypePredicate();

        assertEquals(transactionPredicate, result);
    }

    @Test
    public void shouldReturnAuthorizeTransactionTypePredicate() {
        when(braintreePartialCaptureService.getAuthorizeTransactionTypePredicate()).thenReturn(transactionPredicate);

        Predicate<PaymentTransactionEntryModel> result = unit.getAuthorizeTransactionTypePredicate();

        assertEquals(transactionPredicate, result);
    }

    @Test
    public void shouldReturnRefundTransactionTypePredicate() {
        when(braintreePartialCaptureService.getRefundTransactionTypePredicate()).thenReturn(transactionPredicate);

        Predicate<PaymentTransactionEntryModel> result = unit.getRefundTransactionTypePredicate();

        assertEquals(transactionPredicate, result);
    }

}
