package com.braintree.facade.backoffice.impl;

import com.braintree.command.result.BrainTreeVoidResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.helpers.OrderVoidHelper;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.order.service.BraintreeOrderBackofficeUtilService;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.braintreegateway.Transaction;
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.order.payment.CreditCardPaymentInfoModel;
import de.hybris.platform.core.model.user.UserModel;
import de.hybris.platform.ordermanagementfacades.payment.data.PaymentTransactionEntryData;
import de.hybris.platform.payment.commands.request.VoidRequest;
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 de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.servicelayer.user.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

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.Matchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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

    private static final String SALE_INTENT = "sale";
    private static final String AUTHORIZATION_INTENT = "authorize";
    private static final String PAYMENT_PROVIDER_PROPERTY = "paymentProvider";
    private static final String VENMO_PAYMENT_PROVIDER = "VenmoAccount";

    @Mock
    private BrainTreePaymentService brainTreePaymentService;

    @Mock
    private BrainTreeConfigService brainTreeConfigService;

    @Mock
    private UserService userService;

    @Mock
    private BraintreePaymentInfoService paymentInfoService;

    @Mock
    private BraintreeOrderBackofficeUtilService backofficeUtilService;

    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;

    @Mock
    private Converter<PaymentTransactionEntryModel, PaymentTransactionEntryData> ordermanagementPaymentTransactionEntryConverter;

    @Mock
    private ModelService modelService;

    @Mock
    private OrderVoidHelper orderVoidHelper;

    @InjectMocks
    private DefaultBraintreeBackofficeVoidFacade unit;

    @Mock
    private OrderModel order;
    @Mock
    private BrainTreePaymentInfoModel paymentInfoModel;
    @Mock
    private CreditCardPaymentInfoModel creditCardPaymentInfoModel;
    @Mock
    private PaymentTransactionModel transactionModel;
    @Mock
    private PaymentTransactionEntryModel transactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel authorizationTransactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel captureTransactionEntryModel;
    @Mock
    private PaymentTransactionEntryModel rejectedAuthorizationTransactionEntryModel;
    @Mock
    private UserModel user;
    @Mock
    private BrainTreeVoidResult voidResult;

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

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseIfPaymentIsNotBraintreePaymentInfoModel() {
        when(order.getPaymentInfo()).thenReturn(creditCardPaymentInfoModel);

        boolean result = unit.isVoidPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenSubmitForSettlementConfigIsTrue() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);

        when(paymentInfoModel.getPaymentProvider()).thenReturn(VENMO_PAYMENT_PROVIDER);

        when(brainTreeConfigService.isSubmitForSettlement()).thenReturn(true);

        boolean result = unit.isVoidPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenOrderIsVoided() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);

        when(paymentInfoModel.getPaymentProvider()).thenReturn(VENMO_PAYMENT_PROVIDER);

        when(brainTreeConfigService.isSubmitForSettlement()).thenReturn(false);
        when(backofficeUtilService.isOrderVoided(order)).thenReturn(true);

        boolean result = unit.isVoidPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenOrderAmountCaptured() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);

        when(paymentInfoModel.getPaymentProvider()).thenReturn(VENMO_PAYMENT_PROVIDER);

        when(brainTreeConfigService.isSubmitForSettlement()).thenReturn(false);
        when(backofficeUtilService.isOrderVoided(order)).thenReturn(false);
        when(backofficeUtilService.isOrderAmountCaptured(order)).thenReturn(true);

        boolean result = unit.isVoidPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenOrderCaptured() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(captureTransactionEntryModel, transactionEntryModel));
        when(orderVoidHelper.isSuccessfulCaptureTransaction(transactionEntryModel)).thenReturn(true);

        when(paymentInfoModel.getPaymentProvider()).thenReturn(VENMO_PAYMENT_PROVIDER);

        when(brainTreeConfigService.isSubmitForSettlement()).thenReturn(false);
        when(backofficeUtilService.isOrderVoided(order)).thenReturn(false);
        when(backofficeUtilService.isOrderAmountCaptured(order)).thenReturn(false);

        boolean result = unit.isVoidPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnTrueWhenPaymentVenmoAndSubmitForSettlementConfigIsFalseAndOrderNotVoidedAndOrderNotCaptured() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(authorizationTransactionEntryModel));

        when(paymentInfoModel.getPaymentProvider()).thenReturn(VENMO_PAYMENT_PROVIDER);

        when(brainTreeConfigService.isSubmitForSettlement()).thenReturn(false);
        when(backofficeUtilService.isOrderVoided(order)).thenReturn(false);
        when(backofficeUtilService.isOrderAmountCaptured(order)).thenReturn(false);

        boolean result = unit.isVoidPossible(order);

        assertTrue(result);
    }

    @Test
    public void shouldReturnTrueWhenPaymentMadeByCardAndSubmitForSettlementConfigIsFalseAndOrderNotVoidedAndOrderNotCaptured() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(authorizationTransactionEntryModel));

        when(paymentInfoModel.getPaymentProvider()).thenReturn(PAYMENT_PROVIDER_PROPERTY);

        when(brainTreeConfigService.isSubmitForSettlement()).thenReturn(false);
        when(backofficeUtilService.isOrderVoided(order)).thenReturn(false);
        when(backofficeUtilService.isOrderAmountCaptured(order)).thenReturn(false);
        when(backofficeUtilService.paidByCard(order)).thenReturn(true);

        boolean result = unit.isVoidPossible(order);

        assertTrue(result);
    }

    @Test
    public void shouldReturnFalseWhenOrderIsVoidedAndPaymentNotVenmoOrCard() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);

        when(paymentInfoModel.getPaymentProvider()).thenReturn(PAYMENT_PROVIDER_PROPERTY);

        when(backofficeUtilService.isOrderVoided(order)).thenReturn(true);
        when(backofficeUtilService.paidByCard(order)).thenReturn(false);

        boolean result = unit.isVoidPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenOrderAmountCapturedAndPaymentNotVenmoOrCard() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);

        when(paymentInfoModel.getPaymentProvider()).thenReturn(PAYMENT_PROVIDER_PROPERTY);

        when(backofficeUtilService.isOrderVoided(order)).thenReturn(false);
        when(backofficeUtilService.paidByCard(order)).thenReturn(false);
        when(backofficeUtilService.isOrderAmountCaptured(order)).thenReturn(true);

        boolean result = unit.isVoidPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenIntentSaleAndPaymentNotVenmoOrCard() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(paymentInfoModel.getPayPalIntent()).thenReturn(SALE_INTENT);

        when(paymentInfoModel.getPaymentProvider()).thenReturn(PAYMENT_PROVIDER_PROPERTY);

        when(backofficeUtilService.isOrderVoided(order)).thenReturn(false);
        when(backofficeUtilService.paidByCard(order)).thenReturn(false);
        when(backofficeUtilService.isOrderAmountCaptured(order)).thenReturn(false);

        boolean result = unit.isVoidPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenOrderCapturedAndPaymentNotVenmoOrCard() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(captureTransactionEntryModel, transactionEntryModel));
        when(captureTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.CAPTURE);
        when(transactionEntryModel.getType()).thenReturn(PaymentTransactionType.PARTIAL_CAPTURE);

        when(paymentInfoModel.getPayPalIntent()).thenReturn(AUTHORIZATION_INTENT);
        when(paymentInfoModel.getPaymentProvider()).thenReturn(PAYMENT_PROVIDER_PROPERTY);

        when(backofficeUtilService.isOrderVoided(order)).thenReturn(false);
        when(backofficeUtilService.paidByCard(order)).thenReturn(false);
        when(backofficeUtilService.isOrderAmountCaptured(order)).thenReturn(false);

        boolean result = unit.isVoidPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenNoSuccessfulAuthorizationTransaction() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(authorizationTransactionEntryModel, transactionEntryModel));
        when(authorizationTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(authorizationTransactionEntryModel.getTransactionStatusDetails()).thenReturn("SUCCESFULL");
        when(authorizationTransactionEntryModel.getTransactionStatus()).thenReturn("REJECTED");
        when(transactionEntryModel.getType()).thenReturn(PaymentTransactionType.ORDER);
        when(transactionEntryModel.getTransactionStatusDetails()).thenReturn("NOT_SUCCESFULL");

        when(paymentInfoModel.getPayPalIntent()).thenReturn(AUTHORIZATION_INTENT);
        when(paymentInfoModel.getPaymentProvider()).thenReturn(PAYMENT_PROVIDER_PROPERTY);

        when(backofficeUtilService.isOrderVoided(order)).thenReturn(false);
        when(backofficeUtilService.paidByCard(order)).thenReturn(false);
        when(backofficeUtilService.isOrderAmountCaptured(order)).thenReturn(false);

        boolean result = unit.isVoidPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnTrueWhenIntentAuthorizeAndAuthorizationTransactionPresent() {
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));

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

        when(transactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(transactionEntryModel.getTransactionStatusDetails()).thenReturn("SUCCESFULL");
        when(transactionEntryModel.getTransactionStatus()).thenReturn("ACCEPTED");

        when(paymentInfoModel.getPayPalIntent()).thenReturn(AUTHORIZATION_INTENT);
        when(paymentInfoModel.getPaymentProvider()).thenReturn(PAYMENT_PROVIDER_PROPERTY);

        when(backofficeUtilService.isOrderVoided(order)).thenReturn(false);
        when(backofficeUtilService.paidByCard(order)).thenReturn(false);
        when(backofficeUtilService.isOrderAmountCaptured(order)).thenReturn(false);

        boolean result = unit.isVoidPossible(order);

        assertTrue(result);
    }

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

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

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

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

    @Test
    public void shouldExecuteVoid() throws BraintreeErrorException {
        PaymentTransactionEntryData data = new PaymentTransactionEntryData();
        when(authorizationTransactionEntryModel.getPaymentTransaction()).thenReturn(transactionModel);
        when(transactionModel.getOrder()).thenReturn(order);
        when(brainTreePaymentService.voidTransaction(any(VoidRequest.class))).thenReturn(voidResult);
        when(voidResult.isSuccess()).thenReturn(true);
        when(ordermanagementPaymentTransactionEntryConverter.convert(authorizationTransactionEntryModel)).thenReturn(data);

        PaymentTransactionEntryData result = unit.executeVoid(authorizationTransactionEntryModel);

        assertEquals(data, result);
        verify(authorizationTransactionEntryModel).setTransactionStatus(Transaction.Status.VOIDED.name());
        verify(order).setStatus(OrderStatus.CANCELLED);
    }

    @Test(expected = BraintreeErrorException.class)
    public void shouldThrowExceptionInExecuteVoid() throws BraintreeErrorException {
        PaymentTransactionEntryData data = new PaymentTransactionEntryData();
        when(brainTreePaymentService.voidTransaction(any(VoidRequest.class))).thenReturn(voidResult);
        when(voidResult.isSuccess()).thenReturn(false);

        PaymentTransactionEntryData result = unit.executeVoid(authorizationTransactionEntryModel);
    }

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

        boolean result = unit.isVoidAuthorizationPossible(order);

        assertTrue(result);
    }

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

        boolean result = unit.isVoidAuthorizationPossible(order);

        assertFalse(result);
    }

    @Test
    public void shouldReturnFalseWhenNoSuccessfulAuthorizationTransactionPresent() {
        when(order.getPaymentTransactions()).thenReturn(List.of(transactionModel));
        when(transactionModel.getEntries()).thenReturn(List.of(authorizationTransactionEntryModel));
        when(authorizationTransactionEntryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);
        when(authorizationTransactionEntryModel.getTransactionStatusDetails()).thenReturn("NOT_SUCCESFULL");

        boolean result = unit.isVoidAuthorizationPossible(order);

        assertFalse(result);
    }

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

        boolean result = unit.isVoidAuthorizationPossible(order);

        assertTrue(result);
    }

}
