package com.braintree.controllers;

import com.braintree.email.emailServices.BraintreeLocalPaymentReversedEmailEventService;
import com.braintree.enums.BTFraudToolReviewStatus;
import com.braintree.facade.BrainTreeB2BCheckoutFacade;
import com.braintree.facade.BrainTreeOrderFacade;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.facade.impl.DefaultBrainTreePaymentFacade;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.replenishment.service.BrainTreeReplenishmentService;
import com.braintree.service.BrainTreeFraudCheckToolService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.braintreegateway.LocalPaymentCompleted;
import com.braintreegateway.LocalPaymentReversed;
import com.braintreegateway.RevokedPaymentMethodMetadata;
import com.braintreegateway.Transaction;
import com.braintreegateway.TransactionReview;
import com.braintreegateway.WebhookNotification;
import com.braintreegateway.util.SimpleNodeWrapper;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.basecommerce.model.site.BaseSiteModel;
import de.hybris.platform.commercefacades.order.data.OrderData;
import de.hybris.platform.core.model.order.CartModel;
import de.hybris.platform.core.model.order.OrderModel;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.order.InvalidCartException;
import de.hybris.platform.payment.model.PaymentTransactionModel;
import de.hybris.platform.servicelayer.exceptions.ModelNotFoundException;
import de.hybris.platform.site.BaseSiteService;
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 org.springframework.http.ResponseEntity;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

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

    private static final String PAYMENT_ID = "PaymentId";
    private static final String PAYMENT_METHOD_NONCE = "PaymentMethodNonce";
    private static final String SUCCESS_BODY_RESPONSE = "200";
    private static final String TOKEN = "Token";
    private static final String CUSTOMER_ID = "CustomerId";
    private static final String TRANSACTION_ID = "TransactionId";

    @Spy
    @InjectMocks
    private BraintreeB2BWebhookListener unit;

    @Mock
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;
    @Mock
    private DefaultBrainTreePaymentFacade brainTreePaymentFacade;
    @Mock
    private BrainTreeB2BCheckoutFacade brainTreeB2BCheckoutFacade;
    @Mock
    private BrainTreeOrderFacade brainTreeOrderFacade;
    @Mock
    private BaseSiteService baseSiteService;
    @Mock
    private BraintreePaymentInfoService paymentInfoService;
    @Mock
    private BrainTreeReplenishmentService replenishmentService;
    @Mock
    private BraintreeLocalPaymentReversedEmailEventService localPaymentReversedEmailEventService;
    @Mock
    private BrainTreeFraudCheckToolService btFraudCheckToolService;
    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;
    @Mock
    private HttpServletRequest request;
    @Mock
    private HttpServletResponse response;
    @Mock
    private WebhookNotification webhookNotification;
    @Mock
    private LocalPaymentCompleted localPaymentCompleted;
    @Mock
    private CartModel cart;
    @Mock
    private BaseSiteModel baseSiteModel;
    @Mock
    private OrderData orderData;
    @Mock
    private OrderModel orderModel;
    @Mock
    private SimpleNodeWrapper simpleNodeWrapper;
    @Mock
    private CustomerModel userModel;
    @Mock
    private LocalPaymentReversed localPaymentReversed;
    @Mock
    private RevokedPaymentMethodMetadata revokedPaymentMethodMetadata;
    @Mock
    private PaymentTransactionModel transactionModel;
    @Mock
    private Transaction transaction;

    @Before
    public void setUp(){
        when(brainTreePaymentFacade.getWebhookNotification(request)).thenReturn(webhookNotification);
    }


    @Test
    public void doHandleBillingAgreementHopResponseTest() throws InvalidCartException {
        when(webhookNotification.getLocalPaymentCompleted()).thenReturn(localPaymentCompleted);
        when(localPaymentCompleted.getPaymentId()).thenReturn(PAYMENT_ID);
        when(localPaymentCompleted.getPaymentMethodNonce()).thenReturn(PAYMENT_METHOD_NONCE);
        when(brainTreePaymentFacade.getCartByPaymentId(PAYMENT_ID)).thenReturn(cart);
        when(brainTreeCheckoutFacade.authorizePayment(cart)).thenReturn(true);
        when(brainTreeB2BCheckoutFacade.placeOrderByCart(cart)).thenReturn(orderData);
        when(cart.getSite()).thenReturn(baseSiteModel);

        ResponseEntity<String> responseEntity = unit.doHandleBillingAgreemenHopResponse(request, response);

        verify(baseSiteService).setCurrentBaseSite(cart.getSite(), true);
        verify(brainTreePaymentFacade).updateLocalPaymentMethodSubscription(PAYMENT_METHOD_NONCE, cart);
        verify(brainTreeB2BCheckoutFacade).placeOrderByCart(cart);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void doHandleBillingAgreementHopResponseTestWhenCartIsNullTest() throws InvalidCartException{
        when(webhookNotification.getLocalPaymentCompleted()).thenReturn(localPaymentCompleted);
        when(localPaymentCompleted.getPaymentId()).thenReturn(PAYMENT_ID);
        when(localPaymentCompleted.getPaymentMethodNonce()).thenReturn(PAYMENT_METHOD_NONCE);
        when(brainTreePaymentFacade.getCartByPaymentId(PAYMENT_ID)).thenReturn(null);

        ResponseEntity<String> responseEntity = unit.doHandleBillingAgreemenHopResponse(request, response);

        verify(baseSiteService, never()).setCurrentBaseSite(cart.getSite(), true);
        verify(brainTreePaymentFacade, never()).updateLocalPaymentMethodSubscription(PAYMENT_METHOD_NONCE, cart);
        verify(brainTreeB2BCheckoutFacade, never()).placeOrderByCart(cart);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void doHandleBillingAgreementHopResponseTestWhenPaymentIsNotAuthorizeTest() throws InvalidCartException {
        when(webhookNotification.getLocalPaymentCompleted()).thenReturn(localPaymentCompleted);
        when(localPaymentCompleted.getPaymentId()).thenReturn(PAYMENT_ID);
        when(localPaymentCompleted.getPaymentMethodNonce()).thenReturn(PAYMENT_METHOD_NONCE);
        when(brainTreePaymentFacade.getCartByPaymentId(PAYMENT_ID)).thenReturn(cart);
        when(brainTreeCheckoutFacade.authorizePayment(cart)).thenReturn(false);
        when(cart.getSite()).thenReturn(baseSiteModel);

        ResponseEntity<String> responseEntity = unit.doHandleBillingAgreemenHopResponse(request, response);

        verify(baseSiteService).setCurrentBaseSite(cart.getSite(), true);
        verify(brainTreePaymentFacade).updateLocalPaymentMethodSubscription(PAYMENT_METHOD_NONCE, cart);
        verify(brainTreeB2BCheckoutFacade, never()).placeOrderByCart(cart);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void handleLocalPaymentRefundTest() throws InvalidCartException {
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.LOCAL_PAYMENT_REVERSED);
        when(webhookNotification.getLocalPaymentReversed()).thenReturn(localPaymentReversed);
        when(localPaymentReversed.getPaymentId()).thenReturn(PAYMENT_ID);
        when(brainTreePaymentFacade.getCartByPaymentId(PAYMENT_ID)).thenReturn(cart);
        when(brainTreeB2BCheckoutFacade.placeOrderForLocalPaymentReversed(cart)).thenReturn(orderModel);
        when(cart.getUser()).thenReturn(userModel);

        ResponseEntity<String> responseEntity = unit.handleLocalPaymentRefund(request, response);

        verify(baseSiteService).setCurrentBaseSite(cart.getSite(), true);
        verify(brainTreeCheckoutFacade).processLocalPaymentReversedWebhook(cart);
        verify(localPaymentReversedEmailEventService).sendBraintreeLocalPaymentReversedEmailEvent((CustomerModel) userModel, orderModel);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void handleLocalPaymentRefundWhenKindIncorrectTest() throws InvalidCartException{
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.LOCAL_PAYMENT_EXPIRED);

        ResponseEntity<String> responseEntity = unit.handleLocalPaymentRefund(request, response);

        verify(baseSiteService, never()).setCurrentBaseSite(cart.getSite(), true);
        verify(brainTreeCheckoutFacade, never()).processLocalPaymentReversedWebhook(cart);
        verify(localPaymentReversedEmailEventService, never()).sendBraintreeLocalPaymentReversedEmailEvent((CustomerModel) userModel, orderModel);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void handleLocalPaymentRefundWhenCartByIdIsNullTest() throws InvalidCartException{
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.LOCAL_PAYMENT_REVERSED);
        when(webhookNotification.getLocalPaymentReversed()).thenReturn(localPaymentReversed);
        when(localPaymentReversed.getPaymentId()).thenReturn(PAYMENT_ID);
        when(brainTreePaymentFacade.getCartByPaymentId(PAYMENT_ID)).thenReturn(null);

        ResponseEntity<String> responseEntity = unit.handleLocalPaymentRefund(request, response);

        verify(baseSiteService, never()).setCurrentBaseSite(cart.getSite(), true);
        verify(brainTreeCheckoutFacade, never()).processLocalPaymentReversedWebhook(cart);
        verify(localPaymentReversedEmailEventService, never()).sendBraintreeLocalPaymentReversedEmailEvent((CustomerModel) userModel, orderModel);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void doHandlePaymentMethodWebhookTest() throws InvalidCartException{
        when(webhookNotification.getRevokedPaymentMethodMetadata()).thenReturn(revokedPaymentMethodMetadata);
        when(revokedPaymentMethodMetadata.getToken()).thenReturn(TOKEN);
        when(revokedPaymentMethodMetadata.getCustomerId()).thenReturn(CUSTOMER_ID);

        ResponseEntity<String> responseEntity = unit.doHandlePaymentMethodWebhook(request, response);

        verify(replenishmentService).disableReplenishmentCronJobByPaymentMethodToken(TOKEN);
        verify(paymentInfoService).disable(CUSTOMER_ID, TOKEN);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void doHandlePaymentMethodWebhookWhenRevokedPaymentMethodMetadataTest() throws InvalidCartException{
        when(webhookNotification.getRevokedPaymentMethodMetadata()).thenReturn(null);

        ResponseEntity<String> responseEntity = unit.doHandlePaymentMethodWebhook(request, response);

        verify(replenishmentService, never()).disableReplenishmentCronJobByPaymentMethodToken(TOKEN);
        verify(paymentInfoService, never()).disable(CUSTOMER_ID, TOKEN);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void doHandleBtFraudCheckWebhookTest(){
        TransactionReview transactionReview = new TransactionReview(simpleNodeWrapper);

        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.TRANSACTION_REVIEWED);
        when(webhookNotification.getTransactionReview()).thenReturn(transactionReview);
        when(brainTreeTransactionService.findTransactionByTransactionId(any())).thenReturn(transactionModel);
        when(btFraudCheckToolService.translateStatusFromBtToHybris(any())).thenReturn(BTFraudToolReviewStatus.ACCEPTED);

        ResponseEntity<String> responseEntity = unit.doHandleBtFraudCheckWebhook(request);

        verify(btFraudCheckToolService).updateTransactionStatus(transactionModel, BTFraudToolReviewStatus.ACCEPTED);
        verify(btFraudCheckToolService).saveBtFraudToolReviewStatus(transactionModel, BTFraudToolReviewStatus.ACCEPTED);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void doHandleBtFraudCheckWebhookWithKingIncorrectTest(){
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.LOCAL_PAYMENT_EXPIRED);

        ResponseEntity<String> responseEntity = unit.doHandleBtFraudCheckWebhook(request);

        verify(btFraudCheckToolService, never()).updateTransactionStatus(transactionModel, BTFraudToolReviewStatus.ACCEPTED);
        verify(btFraudCheckToolService, never()).saveBtFraudToolReviewStatus(transactionModel, BTFraudToolReviewStatus.ACCEPTED);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void handleUsBankAccountPaymentTransactionStatusWithTransactionSettledKindTest(){
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.TRANSACTION_SETTLED);

        ResponseEntity<String> responseEntity = unit.handleUsBankAccountPaymentTransactionStatus(request);

        verify(brainTreeOrderFacade).updateOrderStatusWithUsBankAccountPaymentMethod(webhookNotification);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void handleUsBankAccountPaymentTransactionStatusWithTransactionSettledDeclinedKindTest(){
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.TRANSACTION_SETTLEMENT_DECLINED);

        ResponseEntity<String> responseEntity = unit.handleUsBankAccountPaymentTransactionStatus(request);

        verify(brainTreeOrderFacade).updateOrderStatusWithUsBankAccountPaymentMethod(webhookNotification);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void handleUsBankAccountPaymentTransactionStatusWithIncorrectKindTest(){
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.TRANSACTION_REVIEWED);

        ResponseEntity<String> responseEntity = unit.handleUsBankAccountPaymentTransactionStatus(request);

        verify(brainTreeOrderFacade, never()).updateOrderStatusWithUsBankAccountPaymentMethod(webhookNotification);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }

    @Test
    public void handleUsBankAccountPaymentTransactionStatusWithModelNotFoundExceptionTest(){
        doThrow(ModelNotFoundException.class).when(brainTreeOrderFacade).updateOrderStatusWithUsBankAccountPaymentMethod(webhookNotification);

        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.TRANSACTION_SETTLEMENT_DECLINED);
        when(webhookNotification.getTransaction()).thenReturn(transaction);
        when(transaction.getId()).thenReturn(TRANSACTION_ID);

        ResponseEntity<String> responseEntity = unit.handleUsBankAccountPaymentTransactionStatus(request);

        assertEquals(ResponseEntity.ok(SUCCESS_BODY_RESPONSE), responseEntity);
    }
}
