package com.braintree.service;

import com.braintree.command.result.BrainTreeAuthorizationResult;
import com.braintree.command.result.BrainTreeRefundTransactionResult;
import com.braintree.command.result.BrainTreeTransactionResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.enums.BTFraudToolReviewStatus;
import com.braintree.enums.BrainTreePaymentMethod;
import com.braintree.enums.BraintreePaymentTransactionStatus;
import com.braintree.method.impl.DefaultBrainTreePaymentService;
import com.braintree.model.BrainTreePaymentInfoModel;
import com.braintree.service.impl.DefaultBrainTreeFraudCheckToolService;
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.AbstractOrderModel;
import de.hybris.platform.payment.PaymentService;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
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.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

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

    private static final String TRANSACTION_ID = "request id";
    private static final String DECLINE = "DECLINE";
    private static final String CREDIT_CARD = "CreditCard";
    private static final String APPROVED = "APPROVED";
    private static final String PAY_PAL = "PayPal";
    private static final String TRANSACTION_GRAPH_QL_ID = "transaction graphQL id";
    private static final BigDecimal transactionAmount = new BigDecimal("12.4");
    private static final String REFUND_ENTRY_CODE = "refund entry code";
    private static final String CANCEL_ENTRY_CODE = "cancel entry code";
    private static final String REFUND_TRANSACTION_ID = "refund transaction id";
    private static final String REFUND_GRAPHQL_TRANSACTION_ID = "refund graphql transaction id";
    @Mock
    private AbstractOrderModel order;
    @Mock
    private ModelService modelService;
    @Mock
    private PaymentTransactionModel paymentTransaction;
    @Mock
    private Map<String, BTFraudToolReviewStatus> statusMapper;
    @Mock
    private List<BrainTreePaymentMethod> paymentMethodsApplicableForBTFraudToolReview;
    @Mock
    private BrainTreeAuthorizationResult brainTreeAuthorizationResult;
    @Mock
    private BrainTreeConfigService brainTreeConfigService;
    @Mock
    private DefaultBrainTreePaymentService brainTreePaymentService;
    @Mock
    private BrainTreeTransactionResult brainTreeTransactionResult;
    @Mock
    private BrainTreeRefundTransactionResult refundTransactionResult;
    @Mock
    private PaymentService paymentService;
    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;
    @Captor
    private ArgumentCaptor<PaymentTransactionEntryModel> entryModelArgumentCaptor;
    @Captor
    private ArgumentCaptor<List<PaymentTransactionEntryModel>> listEntryModelArgumentCaptor;
    @Mock
    private BrainTreePaymentInfoModel paymentInfoModel;

    @InjectMocks
    private DefaultBrainTreeFraudCheckToolService brainTreeFraudCheckToolService;

    @Before
    public void initSetUp() {
        when(paymentTransaction.getOrder()).thenReturn(order);

        when(statusMapper.get(DECLINE)).thenReturn(BTFraudToolReviewStatus.REJECTED);
        when(statusMapper.get(APPROVED)).thenReturn(BTFraudToolReviewStatus.ACCEPTED);

        when(paymentMethodsApplicableForBTFraudToolReview.contains(BrainTreePaymentMethod.CREDITCARD)).thenReturn(true);
        when(paymentMethodsApplicableForBTFraudToolReview.contains(BrainTreePaymentMethod.PAYPAL)).thenReturn(false);

        when(paymentTransaction.getRequestIdGraphQL()).thenReturn(TRANSACTION_GRAPH_QL_ID);
        when(paymentService.getNewPaymentTransactionEntryCode(paymentTransaction, PaymentTransactionType.REFUND_PARTIAL)).thenReturn(REFUND_ENTRY_CODE);
        when(paymentService.getNewPaymentTransactionEntryCode(paymentTransaction, PaymentTransactionType.CANCEL)).thenReturn(CANCEL_ENTRY_CODE);

    }

    @Test
    public void shouldSaveBtFraudToolReviewStatus() {
        brainTreeFraudCheckToolService.saveBtFraudToolReviewStatus(paymentTransaction, BTFraudToolReviewStatus.FOR_REVIEW);

        verify(order).setBtFraudToolReviewStatus(BTFraudToolReviewStatus.FOR_REVIEW);
        verify(paymentTransaction).setBtFraudToolReviewStatus(BTFraudToolReviewStatus.FOR_REVIEW);
        verify(modelService).saveAll(order, paymentTransaction);
        verify(modelService).refresh(order);
        verify(modelService).refresh(paymentTransaction);
    }

    @Test(expected = IllegalArgumentException.class)
    public void shouldThrowExceptionWhenAnyParameterForSaveIsNull() {
        brainTreeFraudCheckToolService.saveBtFraudToolReviewStatus(paymentTransaction, null);
    }

    @Test
    public void shouldTranslateStatusFromBtToHybris() {
        BTFraudToolReviewStatus actual = brainTreeFraudCheckToolService.translateStatusFromBtToHybris(DECLINE);

        assertEquals(BTFraudToolReviewStatus.REJECTED, actual);
    }

    @Test(expected = IllegalArgumentException.class)
    public void shouldThrowExceptionWhenParameterForTranslateIsNull() {
        brainTreeFraudCheckToolService.translateStatusFromBtToHybris(null);
    }

    @Test
    public void shouldUpdateBtFraudToolReviewStatusWhenDecisionIsPresentAndPaymentProviderApplicable() {
        when(paymentTransaction.getOrder()).thenReturn(order);
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(paymentInfoModel.getPaymentProvider()).thenReturn(CREDIT_CARD);
        when(brainTreeAuthorizationResult.getBtFraudCheckToolDecision()).thenReturn(APPROVED);

        brainTreeFraudCheckToolService.updateBtFraudToolReviewStatus(paymentTransaction, brainTreeAuthorizationResult);

        verify(order).setBtFraudToolReviewStatus(BTFraudToolReviewStatus.ACCEPTED);
        verify(paymentTransaction).setBtFraudToolReviewStatus(BTFraudToolReviewStatus.ACCEPTED);
        verify(modelService).saveAll(order, paymentTransaction);
        verify(modelService).refresh(paymentTransaction);
        verify(modelService).refresh(order);
    }

    @Test
    public void shouldNotUpdateBtFraudToolReviewStatusWhenDecisionIsNull() {
        when(paymentTransaction.getOrder()).thenReturn(order);
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(paymentInfoModel.getPaymentProvider()).thenReturn(CREDIT_CARD);
        when(brainTreeAuthorizationResult.getBtFraudCheckToolDecision()).thenReturn(null);

        brainTreeFraudCheckToolService.updateBtFraudToolReviewStatus(paymentTransaction, brainTreeAuthorizationResult);

        verify(order, never()).setBtFraudToolReviewStatus(any(BTFraudToolReviewStatus.class));
        verify(modelService, never()).save(paymentTransaction);
        verify(modelService, never()).refresh(paymentTransaction);
        verify(paymentTransaction, never()).setBtFraudToolReviewStatus(any(BTFraudToolReviewStatus.class));
        verifyZeroInteractions(modelService);
    }

    @Test
    public void shouldUpdateBtFraudToolReviewStatusWhenDecisionIsNullAndPaymentProviderNotApplicable() {
        when(paymentTransaction.getOrder()).thenReturn(order);
        when(order.getPaymentInfo()).thenReturn(paymentInfoModel);
        when(paymentInfoModel.getPaymentProvider()).thenReturn(PAY_PAL);
        when(brainTreeAuthorizationResult.getBtFraudCheckToolDecision()).thenReturn(null);

        brainTreeFraudCheckToolService.updateBtFraudToolReviewStatus(paymentTransaction, brainTreeAuthorizationResult);

        verify(order).setBtFraudToolReviewStatus(BTFraudToolReviewStatus.NOT_APPLICABLE);
        verify(paymentTransaction).setBtFraudToolReviewStatus(BTFraudToolReviewStatus.NOT_APPLICABLE);
        verify(modelService).saveAll(order, paymentTransaction);
        verify(modelService).refresh(paymentTransaction);
        verify(modelService).refresh(order);
    }

    @Test(expected = IllegalArgumentException.class)
    public void shouldThrowExceptionWhenAnyParameterForUpdateIsNull() {
        brainTreeFraudCheckToolService.updateBtFraudToolReviewStatus(paymentTransaction, null);
    }

    @Test
    public void shouldSuccessfullyUpdateTransactionStatusForGraphQLFlowAndCreateRefundEntry() {
        BrainTreeTransactionResult brainTreeTransactionResult = new BrainTreeTransactionResult();
        BrainTreeRefundTransactionResult refundTransactionResult = new BrainTreeRefundTransactionResult();
        refundTransactionResult.setAmount(transactionAmount);
        refundTransactionResult.setTransactionId(REFUND_TRANSACTION_ID);
        refundTransactionResult.setTransactionGraphQLId(REFUND_GRAPHQL_TRANSACTION_ID);
        brainTreeTransactionResult.setStatus(BraintreePaymentTransactionStatus.SETTLED.getCode());
        brainTreeTransactionResult.setRefunds(List.of(refundTransactionResult));
        brainTreeTransactionResult.setTotalAmount(transactionAmount);

        when(brainTreePaymentService.findTransactionById(TRANSACTION_GRAPH_QL_ID)).thenReturn(brainTreeTransactionResult);

        brainTreeFraudCheckToolService.updateTransactionStatus(paymentTransaction, BTFraudToolReviewStatus.REJECTED);

        verify(paymentTransaction).setEntries(listEntryModelArgumentCaptor.capture());
        verify(modelService).refresh(paymentTransaction);
        verify(brainTreeTransactionService).saveBraintreeTransactionStatus(BraintreePaymentTransactionStatus.REFUNDED.getCode(),
                paymentTransaction);

        PaymentTransactionEntryModel transactionEntry = listEntryModelArgumentCaptor.getValue().get(0);
        assertEquals(TransactionStatus.ACCEPTED.name(), transactionEntry.getTransactionStatus());
        assertEquals(TransactionStatusDetails.SUCCESFULL.name(), transactionEntry.getTransactionStatusDetails());
        assertEquals(PaymentTransactionType.REFUND_PARTIAL, transactionEntry.getType());
        assertEquals(REFUND_TRANSACTION_ID, transactionEntry.getRequestId());
        assertEquals(REFUND_GRAPHQL_TRANSACTION_ID, transactionEntry.getRequestIdGraphQL());
    }

    @Test
    public void shouldSuccessfullyUpdateTransactionStatusForGraphQLFlowAndCreateVoidEntry() {
        BrainTreeTransactionResult brainTreeTransactionResult = new BrainTreeTransactionResult();
        brainTreeTransactionResult.setStatus(BraintreePaymentTransactionStatus.VOIDED.getCode());
        brainTreeTransactionResult.setId(TRANSACTION_ID);
        brainTreeTransactionResult.setGraphQLId(TRANSACTION_GRAPH_QL_ID);
        brainTreeTransactionResult.setRefunds(Collections.emptyList());

        when(brainTreePaymentService.findTransactionById(TRANSACTION_GRAPH_QL_ID)).thenReturn(brainTreeTransactionResult);

        brainTreeFraudCheckToolService.updateTransactionStatus(paymentTransaction, BTFraudToolReviewStatus.REJECTED);

        verify(paymentTransaction).setEntries(listEntryModelArgumentCaptor.capture());
        verify(modelService).refresh(paymentTransaction);
        verify(brainTreeTransactionService).saveBraintreeTransactionStatus(BraintreePaymentTransactionStatus.VOIDED.getCode(),
                paymentTransaction);
        verify(order).setStatus(OrderStatus.CANCELLED);
        verify(modelService).save(order);
        verify(modelService).refresh(order);

        PaymentTransactionEntryModel transactionEntry = listEntryModelArgumentCaptor.getValue().get(0);
        assertEquals(TransactionStatus.ACCEPTED.name(), transactionEntry.getTransactionStatus());
        assertEquals(TransactionStatusDetails.SUCCESFULL.name(), transactionEntry.getTransactionStatusDetails());
        assertEquals(PaymentTransactionType.CANCEL, transactionEntry.getType());
        assertEquals(TRANSACTION_ID, transactionEntry.getRequestId());
        assertEquals(TRANSACTION_GRAPH_QL_ID, transactionEntry.getRequestIdGraphQL());
    }

    @Test
    public void shouldFindTransactionByRequestIdForRestFlow() {
        BrainTreeTransactionResult brainTreeTransactionResult = new BrainTreeTransactionResult();
        brainTreeTransactionResult.setStatus(BraintreePaymentTransactionStatus.AUTHORIZED.getCode());

        when(brainTreePaymentService.findTransactionById(any(String.class))).thenReturn(brainTreeTransactionResult);

        brainTreeFraudCheckToolService.updateTransactionStatus(paymentTransaction, BTFraudToolReviewStatus.FOR_REVIEW);

        verify(brainTreePaymentService).findTransactionById(any(String.class));
    }

    @Test
    public void shouldDoNothingWhenDecisionIsAccept() {
        BrainTreeTransactionResult brainTreeTransactionResult = new BrainTreeTransactionResult();
        brainTreeTransactionResult.setStatus(BraintreePaymentTransactionStatus.SETTLING.getCode());

        when(brainTreePaymentService.findTransactionById(any(String.class))).thenReturn(brainTreeTransactionResult);

        brainTreeFraudCheckToolService.updateTransactionStatus(paymentTransaction, BTFraudToolReviewStatus.ACCEPTED);

        verify(paymentTransaction, never()).setEntries(any());
        verify(modelService, never()).saveAll(any(PaymentTransactionModel.class), any(PaymentTransactionEntryModel.class));
        verify(modelService,never()).refresh(any(PaymentTransactionModel.class));
        verify(modelService, never()).refresh(PaymentTransactionEntryModel.class);
        verify(brainTreeTransactionService, never()).saveBraintreeTransactionStatus(any(String.class), any(PaymentTransactionModel.class));
    }

}
