package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeCustomerRequest;
import com.braintree.command.result.BrainTreeFindCustomerResult;
import com.braintree.command.result.BrainTreeFindCustomersResult;
import com.braintree.command.result.BrainTreePaymentDetailsResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.hybris.data.BrainTreeGraphQLCustomerData;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeSearchPaymentCustomerInput;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeCustomer;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeCustomerConnection;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeErrorDefinition;
import com.braintree.hybris.data.graphql.commands.response.BraintreePaymentMethodData;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentMethodConnection;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentMethodConnectionEdge;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeSearchCustomer;
import com.braintree.model.BraintreeCustomerDetailsModel;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.servicelayer.dto.converter.Converter;
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.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
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.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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

    private static final String DEFINITION_FILE_NAME_CUSTOMERS = "searchCustomer";
    private static final String DEFINITION_FILE_NAME_CUSTOMER = "node";
    private static final String MUTATION_NAME = "search";
    private static final String DATA_MAP_KEY = "data";
    private static final String INPUT_PARAMETER = "input";
    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 MERCHANT_TRANSACTION_CODE = "merchantTransactionCode";
    private static final String CONTAINS_EMAIL_OPERATOR = "CONTAINS";
    private static final String STARTS_WITH_EMAIL_OPERATOR = "STARTS_WITH";
    private static final String ENDS_WITH_EMAIL_OPERATOR = "ENDS_WITH";
    private static final String END_CURSOR = "END_CURSOR";
    private static final String ERROR_MESSAGE = "Error message";
    private static final String ERROR_MAP_KEY = "error";
    private static final String RESULT_ERRORS = "errors";
    private static final String ID_PARAMETER = "id";
    private static final String LEGACY_ID = "legacyId";
    private static final String FIRST_NAME = "firstName";
    private static final String LAST_NAME = "lastName";
    private static final String CUSTOMER_PAYLOAD_ID = "customerPayloadId";
    private static final String AFTER_PARAMETER = "after";

    @Mock
    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    @Mock
    private Converter<BraintreePaymentMethodData, BrainTreePaymentDetailsResult> graphQLPaymentMethodToPaymentDetailsConverter;
    @Mock
    private Converter<BrainTreeCustomerConnection, BrainTreeGraphQLCustomerData> customerSearchResultConverter;
    @Mock
    private ObjectMapper objectMapper;
    @Mock
    private BrainTreeConfigService brainTreeConfigService;

    @Spy
    @InjectMocks
    private DefaultBrainTreeGraphQLFindCustomerCommand command;

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

    @Mock
    private BraintreeCustomerDetailsModel customerDetailsModel;

    private Map<String, Object> mapResult;
    private Map<String, Object> dataResult;
    private BrainTreeCustomer brainTreeCustomerPayload;
    private BrainTreeCustomerRequest customerRequest;
    private BrainTreePaymentMethodConnection paymentMethodConnection;
    private BrainTreeSearchCustomer searchCustomerPayload;
    private BrainTreeCustomerConnection customerConnection;
    private BrainTreeGraphQLCustomerData graphQLCustomerData;

    @Before
    public void setUp() throws IOException {
        graphQLCustomerData = new BrainTreeGraphQLCustomerData();
        graphQLCustomerData.setEndCursor(END_CURSOR);

        paymentMethodConnection = new BrainTreePaymentMethodConnection();
        brainTreeCustomerPayload = new BrainTreeCustomer();
        customerRequest = new BrainTreeCustomerRequest(MERCHANT_TRANSACTION_CODE);
        customerRequest.setCustomerId(CUSTOMER_ID);

        searchCustomerPayload = new BrainTreeSearchCustomer();
        searchCustomerPayload.setCustomers(customerConnection);

        dataResult = new HashMap<>();
        dataResult.put(MUTATION_NAME, brainTreeCustomerPayload);
        dataResult.put(MUTATION_NAME, searchCustomerPayload);
        dataResult.put(DEFINITION_FILE_NAME_CUSTOMER, brainTreeCustomerPayload);
        mapResult = new HashMap<>();
        mapResult.put(DATA_MAP_KEY, dataResult);

        doReturn(mapResult).when(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMER), any());
        doReturn(mapResult).when(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMERS), any());
        when(objectMapper.convertValue(brainTreeCustomerPayload, BrainTreeCustomer.class))
                .thenReturn(brainTreeCustomerPayload);
        when(objectMapper.convertValue(searchCustomerPayload, BrainTreeSearchCustomer.class))
                .thenReturn(searchCustomerPayload);
        when(brainTreeConfigService.getBackofficeSearchLimit()).thenReturn(100);
    }

    @Test
    public void shouldCreateVariablesMapWhenPerform() throws IOException {
        //given
        when(brainTreeCustomerAccountService.getGraphQLIdForCustomer(CUSTOMER_ID)).thenReturn(GRAPH_QL_ID_FOR_CUSTOMER);

        //when
        command.perform(customerRequest);

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

        String input = (String) variables.getValue().get(ID_PARAMETER);

        assertNotNull(input);
        assertEquals(GRAPH_QL_ID_FOR_CUSTOMER, input);
    }

    @Test
    public void shouldTranslateResponseWhenSuccessfulTransactionWhenPerform() throws IOException {
        //given
        brainTreeCustomerPayload.setLegacyId(LEGACY_ID);
        brainTreeCustomerPayload.setId(CUSTOMER_PAYLOAD_ID);
        brainTreeCustomerPayload.setEmail(EMAIL);
        brainTreeCustomerPayload.setFirstName(FIRST_NAME);
        brainTreeCustomerPayload.setLastName(LAST_NAME);

        //when
        BrainTreeFindCustomerResult commandResult = command.perform(customerRequest);

        //then
        assertNotNull(commandResult);
        assertEquals(FIRST_NAME, commandResult.getFirstName());
        assertEquals(LAST_NAME, commandResult.getLastName());
        assertEquals(EMAIL, commandResult.getEmail());
        assertEquals(CUSTOMER_PAYLOAD_ID, commandResult.getGraphQLId());
        assertEquals(LEGACY_ID, commandResult.getId());
        assertNull(commandResult.getPaymentMethods());

        verify(graphQLPaymentMethodToPaymentDetailsConverter, never()).convert(any());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMER), any());
    }

    @Test
    public void shouldSetPaymentMethodsWhenTranslateResponseWhenPerform() throws IOException {
        //given
        BrainTreePaymentDetailsResult paymentDetailsResult = new BrainTreePaymentDetailsResult();

        BraintreePaymentMethodData paymentMethodNode = new BraintreePaymentMethodData();
        BrainTreePaymentMethodConnectionEdge edge = new BrainTreePaymentMethodConnectionEdge();
        edge.setNode(paymentMethodNode);

        paymentMethodConnection.setEdges(List.of(edge));
        brainTreeCustomerPayload.setPaymentMethods(paymentMethodConnection);

        when(graphQLPaymentMethodToPaymentDetailsConverter.convert(paymentMethodNode)).thenReturn(paymentDetailsResult);
        //when
        BrainTreeFindCustomerResult commandResult = command.perform(customerRequest);

        //then
        assertNotNull(commandResult);
        assertNotNull(commandResult.getPaymentMethods());
        assertEquals(1, commandResult.getPaymentMethods().size());
        assertNotNull(commandResult.getPaymentMethods().get(0));
        assertEquals(paymentDetailsResult, commandResult.getPaymentMethods().get(0));

        verify(graphQLPaymentMethodToPaymentDetailsConverter).convert(paymentMethodNode);
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMER), any());
    }

    @Test
    public void shouldNotSetPaymentMethodsWhenEdgesIsEmptyTranslateResponseWhenPerform() throws IOException {
        //given
        paymentMethodConnection.setEdges(Collections.EMPTY_LIST);
        brainTreeCustomerPayload.setPaymentMethods(paymentMethodConnection);

        //when
        BrainTreeFindCustomerResult commandResult = command.perform(customerRequest);

        //then
        assertNotNull(commandResult);
        assertNull(commandResult.getPaymentMethods());

        verify(graphQLPaymentMethodToPaymentDetailsConverter, never()).convert(any());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMER), any());
    }

    @Test
    public void shouldNotSetPaymentMethodsWhenEdgesIsNullTranslateResponseWhenPerform() throws IOException {
        //given
        brainTreeCustomerPayload.setPaymentMethods(paymentMethodConnection);

        //when
        BrainTreeFindCustomerResult commandResult = command.perform(customerRequest);

        //then
        assertNotNull(commandResult);
        assertNull(commandResult.getPaymentMethods());

        verify(graphQLPaymentMethodToPaymentDetailsConverter, never()).convert(any());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMER), any());
    }

    @Test
    public void shouldTranslateResponseWhenPayloadIsNullWhenPerform() throws IOException {
        //given
        when(objectMapper.convertValue(brainTreeCustomerPayload, BrainTreeCustomer.class))
                .thenReturn(null);

        //when
        BrainTreeFindCustomerResult commandResult = command.perform(customerRequest);

        //then
        assertNotNull(commandResult);
        assertNull(commandResult.getFirstName());
        assertNull(commandResult.getLastName());
        assertNull(commandResult.getEmail());
        assertNull(commandResult.getGraphQLId());
        assertNull(commandResult.getId());
        assertNull(commandResult.getPaymentMethods());

        verify(graphQLPaymentMethodToPaymentDetailsConverter, never()).convert(any());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMER), any());
    }

    @Test(expected = IllegalArgumentException.class)
    public void shouldThrowExceptionIfGraphQLResponseContainsError() throws IOException {
        //given
        BrainTreeErrorDefinition brainTreeError = new BrainTreeErrorDefinition();
        brainTreeError.setMessage(ERROR_MESSAGE);
        Map<String, Object> errorMap = Map.of(ERROR_MAP_KEY, brainTreeError);
        List<Map<String, Object>> errorList = new ArrayList<>();
        errorList.add(errorMap);

        mapResult.put(RESULT_ERRORS, errorList);

        when(objectMapper.convertValue(errorMap, BrainTreeErrorDefinition.class)).thenReturn(brainTreeError);

        //when
        command.perform(customerRequest);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMER), any());
        verify(objectMapper).convertValue(errorMap, BrainTreeErrorDefinition.class);
    }

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

        //when
        command.perform(customerRequest);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMER), any());
    }

    @Test
    public void shouldCreateVariablesMapWhenProcess() throws IOException {
        //given
        customerRequest.setCustomerId(CUSTOMER_ID);
        customerRequest.setCustomerEmail(EMAIL);
        customerRequest.setCustomerEmailOperator(CONTAINS_EMAIL_OPERATOR);

        when(brainTreeCustomerAccountService.getGraphQLIdForCustomer(CUSTOMER_ID)).thenReturn(GRAPH_QL_ID_FOR_CUSTOMER);

        //when
        command.process(customerRequest);

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

        BrainTreeSearchPaymentCustomerInput input = (BrainTreeSearchPaymentCustomerInput) variables.getValue().get(INPUT_PARAMETER);

        assertNotNull(input);
        assertNotNull(input.getId());
        assertNotNull(input.getId().getIs());
        assertEquals(GRAPH_QL_ID_FOR_CUSTOMER, input.getId().getIs());

        assertNotNull(input.getEmail());
        assertNotNull(input.getEmail().getContains());
        assertEquals(EMAIL, input.getEmail().getContains());
        assertNull(input.getEmail().getStartsWith());
        assertNull(input.getEmail().getEndsWith());

        verify(brainTreeCustomerAccountService).getGraphQLIdForCustomer(CUSTOMER_ID);
    }

    @Test
    public void shouldSetStartsWithEmailWhenCreateVariablesMapWhenProcess() throws IOException {
        //given
        customerRequest.setCustomerEmail(EMAIL);
        customerRequest.setCustomerEmailOperator(STARTS_WITH_EMAIL_OPERATOR);

        //when
        command.process(customerRequest);

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

        BrainTreeSearchPaymentCustomerInput input = (BrainTreeSearchPaymentCustomerInput) variables.getValue().get(INPUT_PARAMETER);

        assertNotNull(input);
        assertNotNull(input.getEmail());
        assertNotNull(input.getEmail().getStartsWith());
        assertEquals(EMAIL, input.getEmail().getStartsWith());
        assertNull(input.getEmail().getContains());
        assertNull(input.getEmail().getEndsWith());
    }

    @Test
    public void shouldSetEndsWithEmailWhenCreateVariablesMapWhenProcess() throws IOException {
        //given
        customerRequest.setCustomerEmail(EMAIL);
        customerRequest.setCustomerEmailOperator(ENDS_WITH_EMAIL_OPERATOR);

        //when
        command.process(customerRequest);

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

        BrainTreeSearchPaymentCustomerInput input = (BrainTreeSearchPaymentCustomerInput) variables.getValue().get(INPUT_PARAMETER);

        assertNotNull(input);
        assertNotNull(input.getEmail());
        assertNotNull(input.getEmail().getEndsWith());
        assertEquals(EMAIL, input.getEmail().getEndsWith());
        assertNull(input.getEmail().getContains());
        assertNull(input.getEmail().getStartsWith());
    }

    @Test
    public void shouldCreateVariablesMapWhenCustomerEmailAndIdAreNullWhenProcess() throws IOException {
        //given
        customerRequest.setCustomerId(null);
        //when
        command.process(customerRequest);

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

        Map<Object, Object> input = (Map<Object, Object>) variables.getValue().get(INPUT_PARAMETER);

        assertNotNull(input);
    }

    @Test
    public void shouldTranslateCustomerResponse() {
        //given
        graphQLCustomerData.setHasNextPage(false);
        graphQLCustomerData.setCustomerList(List.of(customerDetailsModel));
        when(customerSearchResultConverter.convert(customerConnection)).thenReturn(graphQLCustomerData);

        //when
        BrainTreeFindCustomersResult commandResult = command.process(customerRequest);

        //then
        assertNotNull(commandResult);
        assertNotNull(commandResult.getCustomers());
        assertNotNull(commandResult.getCustomers().get(0));
        assertEquals(customerDetailsModel, commandResult.getCustomers().get(0));
    }

    @Test
    public void shouldTranslateCustomerResponseWhenGraphQLCustomerDataIsNull() {
        //given
        when(customerSearchResultConverter.convert(customerConnection)).thenReturn(null);

        //when
        BrainTreeFindCustomersResult commandResult = command.process(customerRequest);

        //then
        assertNotNull(commandResult);
        assertNotNull(commandResult.getCustomers());
        assertTrue(commandResult.getCustomers().isEmpty());
    }

    @Test
    public void shouldSetExceededLimitTrueWhenTranslateCustomerResponse() {
        //given
        graphQLCustomerData.setHasNextPage(false);
        graphQLCustomerData.setCustomerList(List.of(customerDetailsModel, customerDetailsModel));

        when(customerSearchResultConverter.convert(customerConnection)).thenReturn(graphQLCustomerData);
        when(brainTreeConfigService.getBackofficeSearchLimit()).thenReturn(1);

        //when
        BrainTreeFindCustomersResult commandResult = command.process(customerRequest);

        //then
        assertNotNull(commandResult);
        assertNull(commandResult.getCustomers());
        assertTrue(commandResult.isExceedLimit());
    }

    @Test
    public void shouldSetAfterParameterWhenCreateVariablesMapWhenProcess() throws IOException {
        //given
        graphQLCustomerData.setHasNextPage(true);
        graphQLCustomerData.setCustomerList(List.of(customerDetailsModel));

        when(customerSearchResultConverter.convert(customerConnection)).thenReturn(graphQLCustomerData);
        when(brainTreeConfigService.getBackofficeSearchLimit()).thenReturn(1);

        //when
        command.process(customerRequest);

        //then
        verify(command, times(2)).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMERS), variables.capture());
        assertNotNull(variables.getValue());

        String afterParameter = (String) variables.getValue().get(AFTER_PARAMETER);

        assertNotNull(afterParameter);
        assertEquals(END_CURSOR, afterParameter);
    }

    @Test(expected = IllegalArgumentException.class)
    public void shouldThrowExceptionIfGraphQLResponseContainsErrorWhenProcess() throws IOException {
        //given
        BrainTreeErrorDefinition brainTreeError = new BrainTreeErrorDefinition();
        brainTreeError.setMessage(ERROR_MESSAGE);
        Map<String, Object> errorMap = Map.of(ERROR_MAP_KEY, brainTreeError);
        List<Map<String, Object>> errorList = new ArrayList<>();
        errorList.add(errorMap);

        mapResult.put(RESULT_ERRORS, errorList);

        when(objectMapper.convertValue(errorMap, BrainTreeErrorDefinition.class)).thenReturn(brainTreeError);

        //when
        command.process(customerRequest);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMER), any());
        verify(objectMapper).convertValue(errorMap, BrainTreeErrorDefinition.class);
    }

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

        //when
        command.process(customerRequest);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CUSTOMER), any());
    }

}
