package com.braintree.order.submitForSettlement.service.impl;

import com.braintree.command.result.BrainTreeSubmitForSettlementTransactionResult;
import com.braintree.exceptions.BraintreeErrorException;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.order.adjust.authorization.BraintreeAdjustAuthorizationService;
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.UserModel;
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.ArrayList;
import java.util.List;

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 DefaultBraintreeSubmitForSettlementServiceTest {

    private static final BigDecimal AMOUNT = BigDecimal.TEN;
    private static final String TRANSACTION_ID = "testTransactionId";
    private static final String USER_UID = "userUid";

    @Mock
    private BrainTreePaymentTransactionService brainTreePaymentTransactionService;

    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;

    @Mock
    private BrainTreePaymentService brainTreePaymentService;

    @Mock
    private ModelService modelService;

    @Mock
    private BraintreeAdjustAuthorizationService braintreeAdjustAuthorizationService;

    @InjectMocks
    private DefaultBraintreeSubmitForSettlementService unit;

    @Mock
    private OrderModel orderModel;

    @Mock
    private UserModel userModel;

    @Mock
    private BrainTreeSubmitForSettlementTransactionResult settlementTransactionResult;

    @Mock
    private PaymentTransactionModel paymentTransactionModel;

    @Mock
    private PaymentTransactionEntryModel entryModel;
    @Mock
    private PaymentTransactionEntryModel transactionEntry;

    private List<PaymentTransactionModel> paymentTransactionModels;

    @Before
    public void setUp() {

        when(brainTreePaymentService.submitForSettlementTransaction(any())).thenReturn(settlementTransactionResult);
        when(userModel.getUid()).thenReturn(USER_UID);
        when(orderModel.getUser()).thenReturn(userModel);

        paymentTransactionModels = new ArrayList<>();
        paymentTransactionModels.add(paymentTransactionModel);
        when(brainTreeTransactionService
                .createPaymentTransaction(paymentTransactionModel, settlementTransactionResult, AMOUNT,
                        PaymentTransactionType.CAPTURE, TRANSACTION_ID)).thenReturn(entryModel);
    }

    @Test
    public void submitForSettlementShouldSetOrderStatusAndContinueOrderProcessIfOrderFullyCaptured()
            throws BraintreeErrorException {
        when(settlementTransactionResult.isSuccess()).thenReturn(true);
        when(transactionEntry.getRequestId()).thenReturn(TRANSACTION_ID);
        when(transactionEntry.getPaymentTransaction()).thenReturn(paymentTransactionModel);
        when(brainTreePaymentTransactionService.isOrderFullyCaptured(transactionEntry)).thenReturn(true);

        Assert.assertEquals(entryModel, unit.submitForSettlement(orderModel, AMOUNT, transactionEntry, false));

        verify(brainTreePaymentTransactionService).setOrderStatus(orderModel, OrderStatus.PAYMENT_CAPTURED);
        verify(brainTreePaymentTransactionService).continueOrderProcess(orderModel);
    }

    @Test
    public void submitForSettlementShouldSetOrderStatusAndNotContinueOrderProcessIfOrderNotFullyCaptured()
            throws BraintreeErrorException {
        when(settlementTransactionResult.isSuccess()).thenReturn(true);
        when(transactionEntry.getRequestId()).thenReturn(TRANSACTION_ID);
        when(transactionEntry.getPaymentTransaction()).thenReturn(paymentTransactionModel);
        when(brainTreePaymentTransactionService.isOrderFullyCaptured(transactionEntry)).thenReturn(false);

        Assert.assertEquals(entryModel, unit.submitForSettlement(orderModel, AMOUNT, transactionEntry, false));

        verify(brainTreePaymentTransactionService).setOrderStatus(orderModel, OrderStatus.PARTIAL_CAPTURE);
        verify(brainTreePaymentTransactionService, never()).continueOrderProcess(orderModel);
    }

    @Test
    public void submitForSettlementShouldSetOrderStatusAndContinueOrderProcessWhenForceOrderComplete()
            throws BraintreeErrorException {
        when(settlementTransactionResult.isSuccess()).thenReturn(true);
        when(transactionEntry.getRequestId()).thenReturn(TRANSACTION_ID);
        when(transactionEntry.getPaymentTransaction()).thenReturn(paymentTransactionModel);
        when(brainTreePaymentTransactionService.isOrderFullyCaptured(transactionEntry)).thenReturn(false);

        Assert.assertEquals(entryModel, unit.submitForSettlement(orderModel, AMOUNT, transactionEntry, true));

        verify(brainTreePaymentTransactionService).continueOrderProcess(orderModel);
    }

    @Test
    public void isSubmitForSettlementAvailableWithNotAvailableOrder() {
        Assert.assertFalse(unit.isSubmitForSettlementAvailable(orderModel));
    }

    @Test
    public void isSubmitForSettlementAvailableWithAvailableOrder() {
        when(orderModel.getPaymentTransactions()).thenReturn(paymentTransactionModels);
        Assert.assertTrue(unit.isSubmitForSettlementAvailable(orderModel));
    }

    @Test
    public void isSubmitForSettlementAvailableWhenEntryCapturedAndAccepted() {
        List<PaymentTransactionEntryModel> entryModelList = List.of(entryModel);

        when(orderModel.getPaymentTransactions()).thenReturn(paymentTransactionModels);
        when(paymentTransactionModel.getEntries()).thenReturn(entryModelList);
        when(entryModel.getType()).thenReturn(PaymentTransactionType.CAPTURE);
        when(entryModel.getTransactionStatus()).thenReturn(TransactionStatus.ACCEPTED.name());

        Assert.assertFalse(unit.isSubmitForSettlementAvailable(orderModel));
    }

    @Test
    public void isSubmitForSettlementAvailableWhenEntryAuthorizedAndAccepted() {
        List<PaymentTransactionEntryModel> entryModelList = List.of(entryModel);

        when(orderModel.getPaymentTransactions()).thenReturn(paymentTransactionModels);
        when(paymentTransactionModel.getEntries()).thenReturn(entryModelList);
        when(entryModel.getType()).thenReturn(PaymentTransactionType.AUTHORIZATION);

        Assert.assertTrue(unit.isSubmitForSettlementAvailable(orderModel));
    }

    @Test
    public void shouldProcessErrorResultWhenTransactionIsNotSuccess() throws BraintreeErrorException {
        //given
        when(settlementTransactionResult.isSuccess()).thenReturn(false);
        when(transactionEntry.getPaymentTransaction()).thenReturn(paymentTransactionModel);
        when(transactionEntry.getRequestId()).thenReturn(TRANSACTION_ID);


        //when
        PaymentTransactionEntryModel paymentTransactionEntryModel = unit.submitForSettlement(orderModel, AMOUNT, transactionEntry, false);

        //then
        Assert.assertEquals(entryModel, paymentTransactionEntryModel);
        verify(braintreeAdjustAuthorizationService).checkAndProcessHardDeclineError(settlementTransactionResult, entryModel);
    }

}
