package com.braintree.commands.impl;

import com.braintree.command.request.BrainTreeUpdateCustomerRequest;
import com.braintree.command.result.BrainTreeUpdateCustomerResult;
import com.braintreegateway.Result;
import com.braintreegateway.CustomerRequest;
import com.braintreegateway.CustomerGateway;
import com.braintreegateway.ValidationError;
import com.braintreegateway.ValidationErrorCode;
import com.braintreegateway.ValidationErrors;
import com.braintreegateway.BraintreeGateway;
import com.braintreegateway.Customer;
import com.braintreegateway.exceptions.NotFoundException;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
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.apache.log4j.Logger;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;

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

    private static final String CUSTOMER_ID = "customerId";
    private static final String PAYMENT_METHOD_TOKEN = "paymentMethodToken";
    private static final String CODE = "code";

    @Mock
    private BraintreeGateway mockGateway;
    @Mock
    private Logger logger;
    @Mock
    private Result<Customer> mockResult;
    @Mock
    private CustomerGateway customerGateway;
    @Mock
    private DefaultBrainTreeLoggingHandler loggingHandler;
    @Mock
    private Customer mockCustomer;

    @Spy
    @InjectMocks
    private DefaultBrainTreeSetDefaultPaymentInfoCommand setDefaultPaymentInfoCommand;

    @Test
    public void shouldPerformSuccessWhenNoErrorOccurred() throws NotFoundException {
        BrainTreeUpdateCustomerRequest updateCustomerRequest = createUpdateCustomerRequest();

        doReturn(mockGateway).when(setDefaultPaymentInfoCommand).getBraintreeGateway();
        when(mockGateway.customer()).thenReturn(customerGateway);
        when(customerGateway.update(eq(CUSTOMER_ID), any(CustomerRequest.class))).thenReturn(mockResult);
        when(mockResult.isSuccess()).thenReturn(true);
        when(mockResult.getTarget()).thenReturn(mockCustomer);

        BrainTreeUpdateCustomerResult result = setDefaultPaymentInfoCommand.perform(updateCustomerRequest);

        assertNotNull(result);
        verify(mockGateway.customer()).update(eq(CUSTOMER_ID), any(CustomerRequest.class));
        assertEquals(TransactionStatus.ACCEPTED, result.getTransactionStatus());
        assertEquals(TransactionStatusDetails.SUCCESFULL, result.getTransactionStatusDetails());
        assertEquals(true, result.isSuccess());
    }

    @Test
    public void shouldReturnFailureWhenErrorOccurred() throws NotFoundException {
        BrainTreeUpdateCustomerRequest updateCustomerRequest = createUpdateCustomerRequest();
        ValidationErrors validationErrors = mock(ValidationErrors.class);
        ValidationError error = mock(ValidationError.class);
        List<ValidationError> errorList = Arrays.asList(error);

        doReturn(mockGateway).when(setDefaultPaymentInfoCommand).getBraintreeGateway();
        when(mockGateway.customer()).thenReturn(customerGateway);
        when(customerGateway.update(eq(CUSTOMER_ID), any(CustomerRequest.class))).thenReturn(mockResult);
        when(mockResult.isSuccess()).thenReturn(false);
        when(mockResult.getErrors()).thenReturn(validationErrors);
        when(error.getCode()).thenReturn(ValidationErrorCode.UNKNOWN_VALIDATION_ERROR);
        when(loggingHandler.getLogger()).thenReturn(logger);
        when(validationErrors.getAllDeepValidationErrors()).thenReturn(errorList);

        BrainTreeUpdateCustomerResult result = setDefaultPaymentInfoCommand.perform(updateCustomerRequest);

        assertNotNull(result);
        assertFalse(result.isSuccess());
        verify(mockGateway.customer()).update(eq(CUSTOMER_ID), any(CustomerRequest.class));
    }

    @Test
    public void shouldReturnSuccessFalseWhenWhenNoError() throws NotFoundException {
        BrainTreeUpdateCustomerRequest updateCustomerRequest = createUpdateCustomerRequest();

        doReturn(mockGateway).when(setDefaultPaymentInfoCommand).getBraintreeGateway();
        when(mockGateway.customer()).thenReturn(customerGateway);
        when(customerGateway.update(eq(CUSTOMER_ID), any(CustomerRequest.class))).thenReturn(mockResult);
        when(mockResult.isSuccess()).thenReturn(false);

        BrainTreeUpdateCustomerResult result = setDefaultPaymentInfoCommand.perform(updateCustomerRequest);

        assertNotNull(result);
        assertFalse(result.isSuccess());
        verify(mockGateway.customer()).update(eq(CUSTOMER_ID), any(CustomerRequest.class));
    }

    @Test
    public void shouldReturnSuccessFalseWhenWhenNoDeepErrors() throws NotFoundException {
        BrainTreeUpdateCustomerRequest updateCustomerRequest = createUpdateCustomerRequest();
        ValidationErrors validationErrors = mock(ValidationErrors.class);

        doReturn(mockGateway).when(setDefaultPaymentInfoCommand).getBraintreeGateway();
        when(mockGateway.customer()).thenReturn(customerGateway);
        when(customerGateway.update(eq(CUSTOMER_ID), any(CustomerRequest.class))).thenReturn(mockResult);
        when(mockResult.isSuccess()).thenReturn(false);
        when(mockResult.getErrors()).thenReturn(validationErrors);
        when(validationErrors.getAllDeepValidationErrors()).thenReturn(null);

        BrainTreeUpdateCustomerResult result = setDefaultPaymentInfoCommand.perform(updateCustomerRequest);

        assertNotNull(result);
        assertFalse(result.isSuccess());
        verify(mockGateway.customer()).update(eq(CUSTOMER_ID), any(CustomerRequest.class));
    }

    @Test
    public void shouldReturnSuccessFalseWhenWhenNoErrorCode() throws NotFoundException {
        BrainTreeUpdateCustomerRequest updateCustomerRequest = createUpdateCustomerRequest();
        ValidationErrors validationErrors = mock(ValidationErrors.class);
        ValidationError error = mock(ValidationError.class);
        List<ValidationError> errorList = Arrays.asList(error);

        doReturn(mockGateway).when(setDefaultPaymentInfoCommand).getBraintreeGateway();
        when(mockGateway.customer()).thenReturn(customerGateway);
        when(customerGateway.update(eq(CUSTOMER_ID), any(CustomerRequest.class))).thenReturn(mockResult);
        when(mockResult.isSuccess()).thenReturn(false);
        when(mockResult.getErrors()).thenReturn(validationErrors);
        when(error.getCode()).thenReturn(null);
        when(loggingHandler.getLogger()).thenReturn(logger);
        when(validationErrors.getAllDeepValidationErrors()).thenReturn(errorList);

        BrainTreeUpdateCustomerResult result = setDefaultPaymentInfoCommand.perform(updateCustomerRequest);

        assertNotNull(result);
        assertNull(result.getErrorCode());
        assertFalse(result.isSuccess());
        verify(mockGateway.customer()).update(eq(CUSTOMER_ID), any(CustomerRequest.class));
    }

    @Test
    public void shouldReturnFailureWhenNotFoundErrorOccurred() throws NotFoundException {
        BrainTreeUpdateCustomerRequest updateCustomerRequest = createUpdateCustomerRequest();

        doReturn(mockGateway).when(setDefaultPaymentInfoCommand).getBraintreeGateway();
        when(mockGateway.customer()).thenReturn(customerGateway);
        doReturn(loggingHandler).when(setDefaultPaymentInfoCommand).getLoggingHandler();
        when(loggingHandler.getLogger()).thenReturn(logger);
        when(customerGateway.update(eq(CUSTOMER_ID), any(CustomerRequest.class))).thenThrow(new NotFoundException("Customer not found"));

        BrainTreeUpdateCustomerResult result = setDefaultPaymentInfoCommand.perform(updateCustomerRequest);

        assertNotNull(result);
        assertFalse(result.isSuccess());
        assertEquals("Customer with id=" + CUSTOMER_ID + " not Found!", result.getErrorMessage());
        verify(loggingHandler).getLogger();
        verify(logger).info("Customer with id=" + CUSTOMER_ID + " not Found! Error Customer not found");
    }

    @Test(expected = AdapterException.class)
    public void shouldReturnFailureWhenNErrorOccurred() throws Exception {
        BrainTreeUpdateCustomerRequest updateCustomerRequest = createUpdateCustomerRequest();

        doReturn(mockGateway).when(setDefaultPaymentInfoCommand).getBraintreeGateway();
        when(mockGateway.customer()).thenReturn(customerGateway);
        when(customerGateway.update(eq(CUSTOMER_ID), any(CustomerRequest.class))).thenThrow(new AdapterException());

        setDefaultPaymentInfoCommand.perform(updateCustomerRequest);
    }

    @Test
    public void shouldPerformUnSuccessWhenCustomerIsNull() throws NotFoundException {
        BrainTreeUpdateCustomerRequest updateCustomerRequest = createUpdateCustomerRequest();

        doReturn(mockGateway).when(setDefaultPaymentInfoCommand).getBraintreeGateway();
        when(mockGateway.customer()).thenReturn(customerGateway);
        when(customerGateway.update(eq(CUSTOMER_ID), any(CustomerRequest.class))).thenReturn(mockResult);
        when(mockResult.isSuccess()).thenReturn(true);
        when(mockResult.getTarget()).thenReturn(null);

        BrainTreeUpdateCustomerResult result = setDefaultPaymentInfoCommand.perform(updateCustomerRequest);

        assertNotNull(result);
        verify(mockGateway.customer()).update(eq(CUSTOMER_ID), any(CustomerRequest.class));
        assertNull(result.getTransactionStatus());
        assertNull(result.getTransactionStatusDetails());
        assertFalse(result.isSuccess());
    }

    private BrainTreeUpdateCustomerRequest createUpdateCustomerRequest() {
        BrainTreeUpdateCustomerRequest request = new BrainTreeUpdateCustomerRequest(CODE);
        request.setId(CUSTOMER_ID);
        request.setDefaultPaymentMethodToken(PAYMENT_METHOD_TOKEN);
        return request;
    }
}
