package com.braintree.facade.backoffice.impl;

import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.order.refund.partial.services.BraintreePartialRefundService;
import com.braintree.order.service.BraintreeOrderBackofficeUtilService;
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.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 de.hybris.platform.servicelayer.model.ModelService;
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.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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

    private static final BigDecimal REFUND_AMOUNT = BigDecimal.TEN;
    private static final BigDecimal FULL_CAPTURE_AMOUNT = BigDecimal.valueOf(100);
    private static final BigDecimal FULL_REFUND_AMOUNT = BigDecimal.valueOf(100);

    @Mock
    private BraintreePartialRefundService braintreePartialRefundService;
    @Mock
    private BraintreeOrderBackofficeUtilService braintreeOrderBackofficeUtilService;
    @Mock
    private Converter<PaymentTransactionEntryModel, PaymentTransactionEntryData> ordermanagementPaymentTransactionEntryConverter;
    @Mock
    private ModelService modelService;

    @Mock
    private OrderModel order;
    @Mock
    private PaymentTransactionModel transactionModel;
    @Mock
    private PaymentTransactionEntryModel transactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel authorizationTransactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel captureTransactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel refundTransactionEntryModel;

    @InjectMocks
    private DefaultBraintreeBackofficePartialRefundFacade unit;

    @Test
    public void shouldReturnFalseIfOrderNull() {
        boolean result = unit.isPartialRefundPossible(null);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseIfOrderVoided() {
        when(braintreeOrderBackofficeUtilService.isOrderVoided(order)).thenReturn(true);

        boolean result = unit.isPartialRefundPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseIfNoSuccessfulTransactionPresent() {
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(transactionEntryModel));
        when(transactionEntryModel.getTransactionStatusDetails()).thenReturn("NOT_SUCCESFULL");

        when(braintreeOrderBackofficeUtilService.isOrderVoided(order)).thenReturn(false);

        boolean result = unit.isPartialRefundPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseIfNoAcceptedTransactionPresent() {
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(transactionEntryModel));
        when(transactionEntryModel.getTransactionStatusDetails()).thenReturn("SUCCESFULL");
        when(transactionEntryModel.getTransactionStatus()).thenReturn("REJECTED");

        when(braintreeOrderBackofficeUtilService.isOrderVoided(order)).thenReturn(false);

        boolean result = unit.isPartialRefundPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseIfNoCaptureTransactionPresent() {
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(transactionEntryModel));
        when(transactionEntryModel.getTransactionStatusDetails()).thenReturn("SUCCESFULL");
        when(transactionEntryModel.getTransactionStatus()).thenReturn("ACCEPTED");
        when(transactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);

        when(braintreeOrderBackofficeUtilService.isOrderVoided(order)).thenReturn(false);

        boolean result = unit.isPartialRefundPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseIfAllAmountWasAlreadyRefunded() {
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(captureTransactionEntryModel, refundTransactionEntryModel));
        when(captureTransactionEntryModel.getTransactionStatusDetails()).thenReturn("SUCCESFULL");
        when(captureTransactionEntryModel.getTransactionStatus()).thenReturn("ACCEPTED");
        when(captureTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.CAPTURE);
        when(captureTransactionEntryModel.getAmount()).thenReturn(FULL_CAPTURE_AMOUNT);
        when(refundTransactionEntryModel.getTransactionStatusDetails()).thenReturn("SUCCESFULL");
        when(refundTransactionEntryModel.getTransactionStatus()).thenReturn("ACCEPTED");
        when(refundTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.REFUND_PARTIAL);
        when(refundTransactionEntryModel.getAmount()).thenReturn(FULL_REFUND_AMOUNT);

        when(braintreeOrderBackofficeUtilService.isOrderVoided(order)).thenReturn(false);

        boolean result = unit.isPartialRefundPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnTrueIfNotAllAmountWasRefunded() {
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(captureTransactionEntryModel, refundTransactionEntryModel));
        when(captureTransactionEntryModel.getTransactionStatusDetails()).thenReturn("SUCCESFULL");
        when(captureTransactionEntryModel.getTransactionStatus()).thenReturn("ACCEPTED");
        when(captureTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.PARTIAL_CAPTURE);
        when(captureTransactionEntryModel.getAmount()).thenReturn(FULL_CAPTURE_AMOUNT);
        when(refundTransactionEntryModel.getTransactionStatusDetails()).thenReturn("SUCCESFULL");
        when(refundTransactionEntryModel.getTransactionStatus()).thenReturn("ACCEPTED");
        when(refundTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.REFUND_PARTIAL);
        when(refundTransactionEntryModel.getAmount()).thenReturn(REFUND_AMOUNT);

        when(braintreeOrderBackofficeUtilService.isOrderVoided(order)).thenReturn(false);

        boolean result = unit.isPartialRefundPossible(order);

        assertTrue(result);
    }

    @Test
    public void shouldPerformPartialRefund() throws BraintreeErrorException {
        PaymentTransactionEntryData data = new PaymentTransactionEntryData();
        when(braintreePartialRefundService.partialRefundTransaction(order, captureTransactionEntryModel, FULL_REFUND_AMOUNT)).thenReturn(refundTransactionEntryModel);
        when(ordermanagementPaymentTransactionEntryConverter.convert(refundTransactionEntryModel)).thenReturn(data);

        PaymentTransactionEntryData result = unit.partialRefundTransaction(order, captureTransactionEntryModel, FULL_REFUND_AMOUNT);

        assertEquals(data, result);
        verify(braintreePartialRefundService).partialRefundTransaction(order, captureTransactionEntryModel, FULL_REFUND_AMOUNT);
        verify(ordermanagementPaymentTransactionEntryConverter).convert(refundTransactionEntryModel);
    }

    @Test(expected = BraintreeErrorException.class)
    public void shouldThrowExceptionDuringPartialRefund() throws BraintreeErrorException {
        PaymentTransactionEntryData data = new PaymentTransactionEntryData();
        when(braintreePartialRefundService.partialRefundTransaction(order, captureTransactionEntryModel, FULL_REFUND_AMOUNT)).thenThrow(new BraintreeErrorException("Error during partial capture"));

        unit.partialRefundTransaction(order, captureTransactionEntryModel, FULL_REFUND_AMOUNT);

        verify(braintreePartialRefundService).partialRefundTransaction(order, captureTransactionEntryModel, FULL_REFUND_AMOUNT);
    }

}
