package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeFindTransactionRequest;
import com.braintree.command.result.BrainTreeFindTransactionResult;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.hybris.data.BrainTreeGraphQLTransactionData;
import com.braintree.hybris.data.BraintreeTransactionData;
import com.braintree.hybris.data.BraintreeTransactionEntryData;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeTransactionSearchInput;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeErrorDefinition;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeRefundConnection;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeSearchRefund;
import com.braintree.transaction.service.BrainTreeTransactionService;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.payment.AdapterException;
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.verify;
import static org.mockito.Mockito.when;

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

    private static final String DEFINITION_FILE_NAME = "searchRefund";
    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 MERCHANT_TRANSACTION_CODE = "merchantTransactionCode";
    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";

    @Mock
    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    @Mock
    private BrainTreeTransactionService brainTreeTransactionService;
    @Mock
    private Converter<BrainTreeRefundConnection, BrainTreeGraphQLTransactionData> refundGraphQLSearchResultConverter;
    @Mock
    private ObjectMapper objectMapper;
    @Mock
    private BrainTreeConfigService brainTreeConfigService;

    @Spy
    @InjectMocks
    private DefaultBrainTreeGraphQLFindRefundCommand command;

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

    private Map<String, Object> mapResult;
    private Map<String, Object> dataResult;
    private BrainTreeSearchRefund searchRefundPayload;
    private BrainTreeFindTransactionRequest brainTreeFindTransactionRequest;
    private BrainTreeGraphQLTransactionData graphQLTransactionData;
    private BraintreeTransactionData transactionData;
    private BrainTreeRefundConnection refundConnection;

    @Before
    public void setUp() throws IOException {
        transactionData = new BraintreeTransactionData();
        refundConnection = new BrainTreeRefundConnection();
        graphQLTransactionData = new BrainTreeGraphQLTransactionData();
        graphQLTransactionData.setTransactionData(transactionData);
        searchRefundPayload = new BrainTreeSearchRefund();
        searchRefundPayload.setRefunds(refundConnection);
        brainTreeFindTransactionRequest = new BrainTreeFindTransactionRequest(MERCHANT_TRANSACTION_CODE);

        dataResult = new HashMap<>();
        dataResult.put(MUTATION_NAME, searchRefundPayload);
        mapResult = new HashMap<>();
        mapResult.put(DATA_MAP_KEY, dataResult);

        doReturn(mapResult).when(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        when(objectMapper.convertValue(searchRefundPayload, BrainTreeSearchRefund.class))
                .thenReturn(searchRefundPayload);
        when(brainTreeConfigService.getBackofficeSearchLimit()).thenReturn(100);
    }

    @Test
    public void shouldCreateVariablesMap() throws IOException {
        //given
        brainTreeFindTransactionRequest.setTransactionIds(Collections.EMPTY_LIST);
        brainTreeFindTransactionRequest.setCustomerId(CUSTOMER_ID);

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

        //when
        command.perform(brainTreeFindTransactionRequest);

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

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

        assertNotNull(input);
        assertNotNull(input.getId());
        assertNotNull(input.getId().getIn());

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

        verify(brainTreeCustomerAccountService).getGraphQLIdForCustomer(CUSTOMER_ID);
    }

    @Test
    public void shouldNotPopulateWhenBraintreeFindTransactionRequestIsNullWhenCreateVariablesMap() throws IOException {
        //given
        brainTreeFindTransactionRequest = null;

        //when
        command.perform(brainTreeFindTransactionRequest);

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

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

        assertNull(input);
        verify(brainTreeCustomerAccountService, never()).getGraphQLIdForCustomer(CUSTOMER_ID);
    }

    @Test
    public void shouldNotPopulateWhenBraintreeFindTransactionRequestHasNullValuesWhenCreateVariablesMap() throws IOException {
        //when
        command.perform(brainTreeFindTransactionRequest);

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

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

        assertNotNull(input);
        assertNotNull(input.getId());
        assertNull(input.getId().getIn());

        assertNotNull(input.getCustomer());
        assertNull(input.getCustomer().getId().getIs());

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

    @Test
    public void shouldTranslateResponseWhenSuccessfulTransaction() throws IOException {
        //given
        graphQLTransactionData.setHasNextPage(false);
        graphQLTransactionData.setEndCursor(END_CURSOR);
        transactionData.setTransactionEntries(Collections.EMPTY_LIST);
        when(refundGraphQLSearchResultConverter.convert(refundConnection)).thenReturn(graphQLTransactionData);

        //when
        BrainTreeFindTransactionResult commandResult = command.perform(brainTreeFindTransactionRequest);

        //then
        assertNotNull(commandResult);
        assertNotNull(commandResult.getTransactionData());

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

    @Test
    public void shouldSetExceededLimitTrue() throws IOException {
        //given
        BraintreeTransactionEntryData entryData = new BraintreeTransactionEntryData();
        graphQLTransactionData.setHasNextPage(false);
        graphQLTransactionData.setEndCursor(END_CURSOR);
        transactionData.setTransactionEntries(List.of(entryData, entryData));
        when(refundGraphQLSearchResultConverter.convert(refundConnection)).thenReturn(graphQLTransactionData);
        when(brainTreeConfigService.getBackofficeSearchLimit()).thenReturn(1);

        //when
        BrainTreeFindTransactionResult commandResult = command.perform(brainTreeFindTransactionRequest);

        //then
        assertNotNull(commandResult);
        assertNull(commandResult.getTransactionData());
        assertTrue(commandResult.isExceedLimit());

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

    @Test(expected = AdapterException.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(brainTreeFindTransactionRequest);

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

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

        //when
        command.perform(brainTreeFindTransactionRequest);

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

}
