package com.braintree.controllers;


import com.braintree.email.emailServices.BraintreeLocalPaymentReversedEmailEventService;
import com.braintree.facade.impl.DefaultBrainTreeCheckoutFacade;
import com.braintree.facade.impl.DefaultBrainTreePaymentFacade;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.service.impl.DefaultBrainTreeFraudCheckToolService;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.braintreegateway.LocalPaymentCompleted;
import com.braintreegateway.LocalPaymentReversed;
import com.braintreegateway.RevokedPaymentMethodMetadata;
import com.braintreegateway.TransactionReview;
import com.braintreegateway.WebhookNotification;
import com.braintreegateway.util.SimpleNodeWrapper;
import de.hybris.bootstrap.annotations.UnitTest;
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.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.runners.MockitoJUnitRunner;
import org.springframework.http.HttpStatus;
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.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 BraintreeWebhookListenerTest {

    private static final String PAYMENT_ID = "testId";
    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";

    @Mock
    private DefaultBrainTreePaymentFacade brainTreePaymentFacade;
    @Mock
    private DefaultBrainTreeCheckoutFacade brainTreeCheckoutFacade;
    @Mock
    private HttpServletRequest request;
    @Mock
    private HttpServletResponse response;
    @Mock
    private WebhookNotification webhookNotification;
    @Mock
    private DefaultBrainTreeFraudCheckToolService btFraudCheckToolService;
    @Mock
    private SimpleNodeWrapper simpleNodeWrapper;
    @Mock
    private OrderModel orderModel;
    @Mock
    private CartModel cartModel;
    @Mock
    private LocalPaymentReversed localPaymentReversed;
    @Mock
    private BaseSiteService baseSiteService;
    @Mock
    private BraintreeLocalPaymentReversedEmailEventService localPaymentReversedEmailEventService;
    @Mock
    private CustomerModel customerModel;
    @Mock
    private LocalPaymentCompleted localPaymentCompleted;
    @Mock
    private OrderData orderData;
    @Mock
    private RevokedPaymentMethodMetadata revokedPaymentMethodMetadata;
    @Mock
    private BraintreePaymentInfoService paymentInfoService;
    @Mock
    private PaymentTransactionModel transaction;
    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;

    @InjectMocks
    private BraintreeWebhookListener braintreeWebhookListener;

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

    @Test
    public void doHandleBillingAgreemenHopResponse() 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(cartModel);
        when(brainTreeCheckoutFacade.authorizePayment(cartModel)).thenReturn(true);
        when(brainTreeCheckoutFacade.placeOrderByCart(cartModel)).thenReturn(orderData);

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

        verify(baseSiteService).setCurrentBaseSite(cartModel.getSite(), true);
        verify(brainTreePaymentFacade).updateLocalPaymentMethodSubscription(PAYMENT_METHOD_NONCE, cartModel);
        verify(brainTreeCheckoutFacade).placeOrderByCart(cartModel);

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

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

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

        verify(baseSiteService, never()).setCurrentBaseSite(cartModel.getSite(), true);
        verify(brainTreePaymentFacade, never()).updateLocalPaymentMethodSubscription(PAYMENT_METHOD_NONCE, cartModel);
        verify(brainTreeCheckoutFacade, never()).placeOrderByCart(cartModel);

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

    @Test
    public void doHandleBillingAgreemenHopResponseWhenPaymentNoAuthorize() 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(cartModel);
        when(brainTreeCheckoutFacade.authorizePayment(cartModel)).thenReturn(false);

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

        verify(baseSiteService).setCurrentBaseSite(cartModel.getSite(), true);
        verify(brainTreePaymentFacade).updateLocalPaymentMethodSubscription(PAYMENT_METHOD_NONCE, cartModel);
        verify(brainTreeCheckoutFacade, never()).placeOrderByCart(cartModel);

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

    @Test
    public void handleLocalPaymentRefund() throws InvalidCartException {
        when(localPaymentReversed.getPaymentId()).thenReturn(PAYMENT_ID);
        when(webhookNotification.getLocalPaymentReversed()).thenReturn(localPaymentReversed);
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.LOCAL_PAYMENT_REVERSED);
        when(brainTreePaymentFacade.getWebhookNotification(any())).thenReturn(webhookNotification);
        when(brainTreePaymentFacade.getCartByPaymentId(PAYMENT_ID)).thenReturn(cartModel);
        when(cartModel.getUser()).thenReturn(customerModel);
        when(brainTreeCheckoutFacade.placeOrderForLocalPaymentReversed(cartModel)).thenReturn(orderModel);

        ResponseEntity<String> handleLocalPaymentRefundResult = braintreeWebhookListener
                .handleLocalPaymentRefund(request, response);

        verify(brainTreeCheckoutFacade).processLocalPaymentReversedWebhook(cartModel);
        verify(localPaymentReversedEmailEventService).sendBraintreeLocalPaymentReversedEmailEvent(customerModel, orderModel);

        assertEquals(HttpStatus.OK, handleLocalPaymentRefundResult.getStatusCode());

    }

    @Test
    public void handleLocalPaymentRefundWhenCartNull() throws InvalidCartException {
        when(localPaymentReversed.getPaymentId()).thenReturn(PAYMENT_ID);
        when(webhookNotification.getLocalPaymentReversed()).thenReturn(localPaymentReversed);
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.LOCAL_PAYMENT_REVERSED);
        when(brainTreePaymentFacade.getWebhookNotification(any())).thenReturn(webhookNotification);


        ResponseEntity<String> handleLocalPaymentRefundResult = braintreeWebhookListener
                .handleLocalPaymentRefund(request, response);

        verify(brainTreeCheckoutFacade, never()).processLocalPaymentReversedWebhook(cartModel);
        verify(localPaymentReversedEmailEventService, never()).sendBraintreeLocalPaymentReversedEmailEvent(customerModel, orderModel);

        assertEquals(HttpStatus.OK, handleLocalPaymentRefundResult.getStatusCode());
    }

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

        ResponseEntity<String> handleLocalPaymentRefundResult = braintreeWebhookListener
                .handleLocalPaymentRefund(request, response);

        verify(brainTreeCheckoutFacade, never()).processLocalPaymentReversedWebhook(cartModel);
        verify(localPaymentReversedEmailEventService, never()).sendBraintreeLocalPaymentReversedEmailEvent(customerModel, orderModel);

        assertEquals(HttpStatus.OK, handleLocalPaymentRefundResult.getStatusCode());
    }

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

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

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

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

    @Test
    public void doHandlePaymentMethodWebhookWhenRevokedPaymentMethodMetadataNull(){
        ResponseEntity<String> responseEntity = braintreeWebhookListener
                .doHandlePaymentMethodWebhook(request, response);

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

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

    @Test
    public void shouldNotProcessOtherNotificationThenTransactionReviewNotification() {
        when(brainTreePaymentFacade.getWebhookNotification(request)).thenReturn(webhookNotification);
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.LOCAL_PAYMENT_REVERSED);

        ResponseEntity<String> actual = braintreeWebhookListener.doHandleBtFraudCheckWebhook(request);

        assertEquals(HttpStatus.OK, actual.getStatusCode());

        verifyZeroInteractions(btFraudCheckToolService);
    }

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

        when(brainTreePaymentFacade.getWebhookNotification(request)).thenReturn(webhookNotification);
        when(webhookNotification.getKind()).thenReturn(WebhookNotification.Kind.TRANSACTION_REVIEWED);
        when(webhookNotification.getTransactionReview()).thenReturn(transactionReview);
        when(brainTreeTransactionService.findTransactionByTransactionId(any())).thenReturn(transaction);


        ResponseEntity<String> actual = braintreeWebhookListener.doHandleBtFraudCheckWebhook(request);

        assertEquals(HttpStatus.OK, actual.getStatusCode());

        verify(btFraudCheckToolService).updateTransactionStatus(any(), any());
        verify(btFraudCheckToolService).saveBtFraudToolReviewStatus(any(), any());
    }


}
