package com.braintree.graphql.commands.impl;

import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.hybris.data.graphql.commands.response.BraintreePaymentMethodData;
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 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.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 DefaultBrainTreeGetPaymentDetailsByNonceTest {

    private static final String ID_PARAMETER = "id";
    private static final String NONCE = "nonce";
    private static final String DATA_MAP_KEY = "data";
    private static final String DEFINITION_FILE_NAME = "getThreeDSDetailsByNonce";
    private static final String NODE = "node";
    private static final String RESULT_ERRORS = "errors";
    private static final String ERROR_MESSAGE = "errorMessage";
    private static final String ERROR_ATTRIBUTE = "errorAttribute";
    private static final String ERROR_KEY_MAP = "error";
    private static final String BRAINTREE_USD = "Braintree_USD";

    @Mock
    private ObjectMapper objectMapper;
    @Mock
    private BrainTreeConfigService brainTreeConfigService;

    @Spy
    @InjectMocks
    private DefaultBrainTreeGetPaymentDetailsByNonce command;

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

    private Map<String, Object> nodeMap;
    private Map<String, Object> mapResult;
    private BraintreePaymentMethodData brainTreePaymentMethod;
    private Object node;

    @Before
    public void setUp() throws IOException {
        brainTreePaymentMethod = new BraintreePaymentMethodData();

        node = new Object();
        nodeMap = new HashMap<>();
        nodeMap.put(NODE, node);
        mapResult = new HashMap<>();
        mapResult.put(DATA_MAP_KEY, nodeMap);

        doReturn(mapResult).when(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        when(objectMapper.convertValue(node, BraintreePaymentMethodData.class)).thenReturn(brainTreePaymentMethod);
        when(brainTreeConfigService.getMerchantAccountIdForCurrentSiteAndCurrency()).thenReturn(BRAINTREE_USD);
    }

    @Test
    public void shouldCreateVariablesMap() throws IOException {
        //when
        command.perform(NONCE);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), variables.capture());
        assertEquals(NONCE, variables.getValue().get(ID_PARAMETER));
    }

    @Test
    public void shouldTranslateResponse() throws IOException {
        //when
        BraintreePaymentMethodData commandResult = command.perform(NONCE);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        verify(objectMapper).convertValue(node, BraintreePaymentMethodData.class);
        assertEquals(brainTreePaymentMethod, commandResult);
    }

    @Test(expected = AdapterException.class)
    public void shouldThrowErrorWhenGraphQLResponseContainsError() throws IOException {
        //given
        ValidationError validationError =
                new ValidationError(ERROR_ATTRIBUTE, ValidationErrorCode.UNKNOWN_VALIDATION_ERROR, ERROR_MESSAGE);
        Map<String, Object> errorMap = Map.of(ERROR_KEY_MAP, validationError);
        List<Map<String, Object>> errorList = new ArrayList<>();
        errorList.add(errorMap);
        mapResult.put(RESULT_ERRORS, errorList);

        //when
        command.perform(NONCE);

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

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

        //when
        command.perform(NONCE);

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

}
