package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeUpdateCustomerRequest;
import com.braintree.command.result.BrainTreeUpdateCustomerResult;
import com.braintree.commands.impl.DefaultBrainTreeLoggingHandler;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeCustomerInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeUpdateCustomerInput;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeCustomer;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeUpdateCustomerPayload;
import com.braintreegateway.ValidationError;
import com.braintreegateway.ValidationErrorCode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
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.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
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 DefaultBrainTreeGraphQLUpdateCustomerCommandTest {

    private static final String DEFINITION_FILE_NAME = "updateCustomer";
    private static final String DATA_MAP_KEY = "data";
    private static final String INPUT_PARAMETER = "input";
    private static final String RESULT_ERRORS = "errors";
    private static final String ERROR_MESSAGE = "Error message";
    private static final String ERROR_MAP_KEY = "error";
    private static final String CUSTOMER_ID = "customerId";
    private static final String GRAPH_QL_ID_FOR_CUSTOMER = "graphQlIdForCustomer";
    private static final String EMAIL = "email";
    private static final String FIRST_NAME = "firstName";
    private static final String LAST_NAME = "lastName";
    private static final String MERCHANT_TRANSACTION_CODE = "merchantTransactionCode";
    private static final String UPDATE_CUSTOMER_REQUEST_ID = "updateCustomerRequestId";
    private static final String COMPANY = "company";
    private static final String PHONE = "phone";

    @Mock
    private DefaultBrainTreeLoggingHandler loggingHandler;
    @Mock
    private ObjectMapper objectMapper;
    @Mock
    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;

    @Spy
    @InjectMocks
    private DefaultBrainTreeGraphQLUpdateCustomerCommand command;

    @Mock
    private Logger logger;

    @Captor
    private ArgumentCaptor<Map<String, Object>> variables;

    private Map<String, Object> mapResult;
    private Map<String, Object> dataResult;
    private BrainTreeUpdateCustomerRequest updateCustomerRequest;
    private BrainTreeUpdateCustomerPayload updateCustomerPayload;

    @Before
    public void setUp() throws IOException {
        updateCustomerRequest = new BrainTreeUpdateCustomerRequest(MERCHANT_TRANSACTION_CODE);
        updateCustomerRequest.setEmail(EMAIL);
        updateCustomerRequest.setFirstName(FIRST_NAME);
        updateCustomerRequest.setLastName(LAST_NAME);
        updateCustomerRequest.setCompany(COMPANY);
        updateCustomerRequest.setPhone(PHONE);

        updateCustomerPayload = new BrainTreeUpdateCustomerPayload();

        dataResult = new HashMap<>();
        dataResult.put(DEFINITION_FILE_NAME, updateCustomerPayload);
        mapResult = new HashMap<>();
        mapResult.put(DATA_MAP_KEY, dataResult);

        doReturn(mapResult).when(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        when(objectMapper.convertValue(updateCustomerPayload, BrainTreeUpdateCustomerPayload.class))
                .thenReturn(updateCustomerPayload);
        when(loggingHandler.getLogger()).thenReturn(logger);
    }

    @Test
    public void shouldCreateVariablesMap() throws IOException {
        //given
        updateCustomerRequest.setId(UPDATE_CUSTOMER_REQUEST_ID);
        when(brainTreeCustomerAccountService.getGraphQLIdForCustomer(UPDATE_CUSTOMER_REQUEST_ID))
                .thenReturn(GRAPH_QL_ID_FOR_CUSTOMER);

        //when
        command.perform(updateCustomerRequest);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), variables.capture());
        assertNotNull(variables.getValue());

        BrainTreeUpdateCustomerInput input = (BrainTreeUpdateCustomerInput) variables.getValue().get(INPUT_PARAMETER);
        assertNotNull(input);
        assertEquals(GRAPH_QL_ID_FOR_CUSTOMER, input.getCustomerId());

        BrainTreeCustomerInput inputCustomer = input.getCustomer();
        assertNotNull(inputCustomer);
        assertEquals(FIRST_NAME, inputCustomer.getFirstName());
        assertEquals(LAST_NAME, inputCustomer.getLastName());
        assertEquals(EMAIL, inputCustomer.getEmail());
        assertEquals(PHONE, inputCustomer.getPhoneNumber());
        assertEquals(COMPANY, inputCustomer.getCompany());
    }

    @Test
    public void shouldNotPopulateInputCustomerIdWhenRequestIdIsNull() throws IOException {
        //when
        command.perform(updateCustomerRequest);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), variables.capture());
        assertNotNull(variables.getValue());

        BrainTreeUpdateCustomerInput input = (BrainTreeUpdateCustomerInput) variables.getValue().get(INPUT_PARAMETER);
        assertNotNull(input);
        assertNull(input.getCustomerId());

        BrainTreeCustomerInput inputCustomer = input.getCustomer();
        assertNotNull(inputCustomer);
        assertEquals(FIRST_NAME, inputCustomer.getFirstName());
        assertEquals(LAST_NAME, inputCustomer.getLastName());
        assertEquals(EMAIL, inputCustomer.getEmail());
        assertEquals(PHONE, inputCustomer.getPhoneNumber());
        assertEquals(COMPANY, inputCustomer.getCompany());

        verify(brainTreeCustomerAccountService, never()).getGraphQLIdForCustomer(anyString());
    }

    @Test
    public void shouldTranslateResponseWhenSuccessfulTransaction() throws IOException {
        //given
        BrainTreeCustomer brainTreeCustomer = new BrainTreeCustomer();
        brainTreeCustomer.setId(CUSTOMER_ID);
        updateCustomerPayload.setCustomer(brainTreeCustomer);

        //when
        BrainTreeUpdateCustomerResult commandResult = command.perform(updateCustomerRequest);

        //then
        assertNotNull(commandResult);
        assertTrue(commandResult.isSuccess());
        assertNotNull(commandResult.getCustomer());
        assertEquals(brainTreeCustomer, commandResult.getCustomer());
        assertEquals(CUSTOMER_ID, commandResult.getGraphQLId());
        assertEquals(TransactionStatus.ACCEPTED, commandResult.getTransactionStatus());
        assertEquals(TransactionStatusDetails.SUCCESFULL, commandResult.getTransactionStatusDetails());

        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        verify(loggingHandler).handleResult(brainTreeCustomer);
    }

    @Test
    public void shouldTranslateResponseWhenPayloadIsNull() throws IOException {
        //given
        when(objectMapper.convertValue(updateCustomerPayload, BrainTreeUpdateCustomerPayload.class))
                .thenReturn(null);

        //when
        BrainTreeUpdateCustomerResult commandResult = command.perform(updateCustomerRequest);

        //then
        assertNotNull(commandResult);
        assertFalse(commandResult.isSuccess());
        assertNull(commandResult.getCustomer());
        assertNull(commandResult.getCustomer());
        assertNull(commandResult.getGraphQLId());
        assertNull(commandResult.getTransactionStatus());
        assertNull(commandResult.getTransactionStatusDetails());

        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        verify(loggingHandler, never()).handleResult(any(BrainTreeCustomer.class));
    }

    @Test
    public void shouldTranslateResponseWhenPayloadCustomerIsNull() throws IOException {
        //when
        BrainTreeUpdateCustomerResult commandResult = command.perform(updateCustomerRequest);

        //then
        assertNotNull(commandResult);
        assertFalse(commandResult.isSuccess());
        assertNull(commandResult.getCustomer());
        assertNull(commandResult.getCustomer());
        assertNull(commandResult.getGraphQLId());
        assertNull(commandResult.getTransactionStatus());
        assertNull(commandResult.getTransactionStatusDetails());

        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        verify(loggingHandler, never()).handleResult(any(BrainTreeCustomer.class));
    }

    @Test
    public void shouldTranslateResponseWhenUnsuccessfulTransaction() throws IOException {
        //given
        ValidationError validationError =
                new ValidationError(StringUtils.EMPTY, ValidationErrorCode.UNKNOWN_VALIDATION_ERROR, ERROR_MESSAGE);
        Map<String, Object> errorMap = Map.of(ERROR_MAP_KEY, validationError);
        List<Map<String, Object>> errorList = new ArrayList<>();
        errorList.add(errorMap);

        mapResult.put(RESULT_ERRORS, errorList);

        when(objectMapper.convertValue(errorMap, ValidationError.class)).thenReturn(validationError);

        //when
        BrainTreeUpdateCustomerResult commandResult = command.perform(updateCustomerRequest);

        //then
        assertNotNull(commandResult);
        assertFalse(commandResult.isSuccess());
        assertEquals(ValidationErrorCode.UNKNOWN_VALIDATION_ERROR.toString(), commandResult.getErrorCode());
        assertEquals(ERROR_MESSAGE, commandResult.getErrorMessage());

        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        verify(loggingHandler).getLogger();
    }

    @Test(expected = AdapterException.class)
    public void shouldThrowErrorWhenGraphQLCallFails() throws IOException {
        //given
        doThrow(IOException.class).when(command).makeGraphQlCall(any(), any());

        //when
        command.perform(updateCustomerRequest);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        verify(loggingHandler, never()).getLogger();
    }

}
