package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeTokenizeCreditCardRequest;
import com.braintree.command.result.BrainTreeTokenizeCardResult;
import com.braintree.commands.impl.DefaultBrainTreeLoggingHandler;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeCreditCardInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeTokenizeCreditCardInput;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeErrorDefinition;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeErrorExtension;
import com.braintree.hybris.data.graphql.commands.response.BraintreePaymentMethodData;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeTokenizeCreditCardPayload;
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.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
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 DefaultBrainTreeGraphQLTokenizeCreditCardCommandTest {

    private static final String DEFINITION_FILE_NAME_TOKENIZE_CARD = "tokenizeCreditCard";
    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 LEGACY_ID = "legacyId";
    private static final String LEGACY_CODE = "legacyCode";
    private static final String ERROR_MAP_KEY = "error";
    private static final String CARD_HOLDER_FULL_NAME = "John Cena";
    private static final String CARD_NUMBER = "4111111111111111";
    private static final String CV_2_NUMBER = "413";
    private static final int EXPIRATION_MONTH = 1;
    private static final int EXPIRATION_YEAR = 2030;
    private static final String PAYMENT_METHOD_ID = "paymentMethodId";

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

    @Spy
    @InjectMocks
    private DefaultBrainTreeGraphQLTokenizeCreditCardCommand command;

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

    private Map<String, Object> mapResult;
    private Map<String, Object> dataResult;
    private BrainTreeTokenizeCreditCardRequest tokenizeCreditCardRequest;
    private BrainTreeTokenizeCreditCardPayload tokenizeCreditCardPayload;
    private BraintreePaymentMethodData paymentMethod;

    @Before
    public void setUp() throws IOException {
        paymentMethod = new BraintreePaymentMethodData();
        tokenizeCreditCardPayload = new BrainTreeTokenizeCreditCardPayload();
        tokenizeCreditCardPayload.setPaymentMethod(paymentMethod);
        tokenizeCreditCardRequest = new BrainTreeTokenizeCreditCardRequest();


        dataResult = new HashMap<>();
        dataResult.put(DEFINITION_FILE_NAME_TOKENIZE_CARD, tokenizeCreditCardPayload);
        mapResult = new HashMap<>();
        mapResult.put(DATA_MAP_KEY, dataResult);

        doReturn(mapResult).when(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_TOKENIZE_CARD), any());
        when(objectMapper.convertValue(tokenizeCreditCardPayload, BrainTreeTokenizeCreditCardPayload.class))
                .thenReturn(tokenizeCreditCardPayload);
    }

    @Test
    public void shouldCreateVariablesMap() throws IOException {
        //given
        tokenizeCreditCardRequest.setCardHolderName(CARD_HOLDER_FULL_NAME);
        tokenizeCreditCardRequest.setCardNumber(CARD_NUMBER);
        tokenizeCreditCardRequest.setCvv(CV_2_NUMBER);
        tokenizeCreditCardRequest.setExpirationMonth(EXPIRATION_MONTH);
        tokenizeCreditCardRequest.setExpirationYear(EXPIRATION_YEAR);

        //when
        command.perform(tokenizeCreditCardRequest);

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

        BrainTreeTokenizeCreditCardInput input = (BrainTreeTokenizeCreditCardInput) variables.getValue().get(INPUT_PARAMETER);
        assertNotNull(input);

        BrainTreeCreditCardInput creditCard = input.getCreditCard();
        assertNotNull(creditCard);
        assertEquals(CARD_HOLDER_FULL_NAME, creditCard.getCardholderName());
        assertEquals(CARD_NUMBER, creditCard.getNumber());
        assertEquals(EXPIRATION_MONTH, creditCard.getExpirationMonth().intValue());
        assertEquals(EXPIRATION_YEAR, creditCard.getExpirationYear().intValue());

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

    @Test
    public void shouldTranslateResponseWhenSuccessfulTransaction() throws IOException {
        //given
        paymentMethod.setLegacyId(LEGACY_ID);
        paymentMethod.setId(PAYMENT_METHOD_ID);

        //when
        BrainTreeTokenizeCardResult commandResult = command.perform(tokenizeCreditCardRequest);

        //then
        assertNotNull(commandResult);
        assertEquals(LEGACY_ID, commandResult.getMethodId());
        assertEquals(PAYMENT_METHOD_ID, commandResult.getMethodGraphQLId());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_TOKENIZE_CARD), any());
    }

    @Test
    public void shouldTranslateResponseWhenSuccessfulTransactionAndPaymentMethodIsNull() throws IOException {
        //given
        tokenizeCreditCardPayload.setPaymentMethod(null);

        //when
        BrainTreeTokenizeCardResult commandResult = command.perform(tokenizeCreditCardRequest);

        //then
        assertNotNull(commandResult);
        assertNull(commandResult.getMethodId());
        assertNull(commandResult.getMethodGraphQLId());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_TOKENIZE_CARD), any());
    }

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

        //when
        command.perform(tokenizeCreditCardRequest);

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

    @Test(expected = AdapterException.class)
    public void shouldThrowExceptionWhenGraphQLResponseContainsError() throws IOException {
        //given
        BrainTreeErrorExtension errorExtension = new BrainTreeErrorExtension();
        errorExtension.setLegacyCode(LEGACY_CODE);
        BrainTreeErrorDefinition brainTreeError = new BrainTreeErrorDefinition();
        brainTreeError.setMessage(ERROR_MESSAGE);
        brainTreeError.setExtensions(errorExtension);
        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(tokenizeCreditCardRequest);

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

}
