package com.braintree.graphql.commands.impl;

import com.braintree.command.request.BrainTreeSaleTransactionRequest;
import com.braintree.command.request.BrainTreeTokenizeCreditCardRequest;
import com.braintree.command.result.BrainTreeSaleTransactionResult;
import com.braintree.command.result.BrainTreeTokenizeCardResult;
import com.braintree.commands.impl.DefaultBrainTreeLoggingHandler;
import com.braintree.configuration.service.BrainTreeConfigService;
import com.braintree.customer.service.BrainTreeCustomerAccountService;
import com.braintree.helpers.SubmitForSettlementHelper;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeAddressInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeChargePaymentMethodInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeTransactionDescriptorInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeTransactionInput;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeVaultPaymentMethodCriteria;
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.BrainTreeMonetaryAmount;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentMethodSnapshot;
import com.braintree.hybris.data.graphql.commands.response.BrainTreePaymentStatus;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeTransaction;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeTransactionPayload;
import com.braintree.method.BrainTreePaymentService;
import com.braintree.payment.info.service.BraintreePaymentInfoService;
import com.braintree.service.impl.DefaultBrainTreeDescriptorService;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hybris.bootstrap.annotations.UnitTest;
import de.hybris.platform.payment.AdapterException;
import de.hybris.platform.payment.dto.BillingInfo;
import de.hybris.platform.payment.dto.CardInfo;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
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.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Currency;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
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.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
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 DefaultBrainTreeGraphQLSaleTransactionCommandTest {

    private static final String DEFINITION_FILE_NAME_CHARGE_PAYPAL = "chargePayPalAccount";
    private static final String DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL = "authorizePayPalAccount";
    private static final String DATA_MAP_KEY = "data";
    private static final String AMOUNT = "12.44";
    private static final String INPUT_PARAMETER = "input";
    private static final String MERCHANT_ACCOUNT_ID = "merchantAccountId";
    private static final String USD = "USD";
    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 PAYPAL_ACCOUNT_INSTRUMENT_TYPE = "paypal_account";
    private static final String CAPTURE_ID = "captureId";
    private static final String LEGACY_CODE = "legacyCode";
    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 MERCHANT_ACCOUNT_ID_FOR_CURRENT_SITE = "merchantAccountIdForCurrentSite";
    private static final String EMAIL = "email";
    private static final String BRAINTREE_CHANNEL = "braintreeChannel";
    private static final String CUSTOM_FIELD_KEY = "customFieldKey";
    private static final String CUSTOM_FIELD_VALUE = "customFieldValue";
    private static final String PAYMENT_METHOD_TOKEN = "paymentMethodToken";
    private static final String GRAPH_QL_TOKEN_FOR_PAYMENT_METHOD = "GraphQLTokenForPaymentMethod";
    private static final String NONCE = "nonce";
    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 METHOD_GRAPH_QL_ID = "methodGraphQlId";
    private static final String STATE = "state";
    private static final String COUNTRY = "country";
    private static final String FIRST_NAME = "firstName";
    private static final String LAST_NAME = "lastName";
    private static final String STREET_1 = "street1";
    private static final String POSTAL_CODE = "postalCode";
    private static final String CITY = "city";
    private static final String REGION = "region";
    private static final String SALE_TRANSACTION_ID = "saleTransactionId";
    private static final String MERCHANT_TRANSACTION_CODE = "merchantTransactionCode";

    @Mock
    private BrainTreeCustomerAccountService brainTreeCustomerAccountService;
    @Mock
    private BraintreePaymentInfoService paymentInfoService;
    @Mock
    private BrainTreePaymentService brainTreePaymentService;
    @Mock
    private SubmitForSettlementHelper submitForSettlementHelper;
    @Mock
    private DefaultBrainTreeDescriptorService descriptorService;
    @Mock
    private DefaultBrainTreeLoggingHandler loggingHandler;
    @Mock
    private ObjectMapper objectMapper;
    @Mock
    private BrainTreeConfigService brainTreeConfigService;

    @Spy
    @InjectMocks
    private DefaultBrainTreeGraphQLSaleTransactionCommand command;

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

    @Captor
    private ArgumentCaptor<BrainTreeTokenizeCreditCardRequest> cardRequestArgumentCaptor;

    @Mock
    private Logger logger;

    private Map<String, Object> mapResult;
    private Map<String, Object> dataResult;
    private BigDecimal bigDecimalAmount;
    private BrainTreeTransactionPayload transactionPayload;
    private Calendar createdAt;
    private BrainTreePaymentMethodSnapshot paymentMethodSnapshot;
    private BrainTreeTransaction saleTransaction;
    private BillingInfo billingInfo;
    private BrainTreeSaleTransactionRequest saleRequest;

    @Before
    public void setUp() throws IOException {
        saleTransaction = new BrainTreeTransaction();
        transactionPayload = new BrainTreeTransactionPayload();
        transactionPayload.setTransaction(saleTransaction);

        billingInfo = new BillingInfo();
        billingInfo.setCountry(COUNTRY);
        billingInfo.setFirstName(FIRST_NAME);
        billingInfo.setLastName(LAST_NAME);
        billingInfo.setStreet1(STREET_1);
        billingInfo.setPostalCode(POSTAL_CODE);
        billingInfo.setCity(CITY);

        bigDecimalAmount = new BigDecimal(AMOUNT);
        createdAt = new GregorianCalendar();

        dataResult = new HashMap<>();
        dataResult.put(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL, transactionPayload);
        mapResult = new HashMap<>();
        mapResult.put(DATA_MAP_KEY, dataResult);

        paymentMethodSnapshot = new BrainTreePaymentMethodSnapshot();
        paymentMethodSnapshot.setCaptureId(CAPTURE_ID);

        saleRequest = new BrainTreeSaleTransactionRequest(MERCHANT_TRANSACTION_CODE, null,
                Currency.getInstance(Locale.US), bigDecimalAmount, billingInfo);

        when(descriptorService.isDescriptorAvailable(PAYPAL_ACCOUNT_INSTRUMENT_TYPE)).thenReturn(true);
        doReturn(mapResult).when(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
        when(objectMapper.convertValue(transactionPayload, BrainTreeTransactionPayload.class))
                .thenReturn(transactionPayload);
        when(loggingHandler.getLogger()).thenReturn(logger);
    }

    @Test
    public void shouldCreateVariablesMap() throws IOException {
        //given
        saleRequest.setTaxAmount(bigDecimalAmount);
        saleRequest.setCustomerId(CUSTOMER_ID);
        saleRequest.setUsePaymentMethodToken(false);
        saleRequest.setMerchantAccountIdForCurrentSite(MERCHANT_ACCOUNT_ID_FOR_CURRENT_SITE);

        when(brainTreeCustomerAccountService.getGraphQLIdForCustomer(CUSTOMER_ID)).thenReturn(GRAPH_QL_ID_FOR_CUSTOMER);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(true);
        when(brainTreeConfigService.getBrainTreeChannel()).thenReturn(BRAINTREE_CHANNEL);
        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);

        //when
        command.perform(saleRequest);

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

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

        assertNotNull(input);
        BrainTreeTransactionInput transaction = input.getTransaction();
        assertNotNull(transaction);
        assertEquals(bigDecimalAmount, transaction.getAmount());
        assertNotNull(transaction.getTax());
        assertEquals(bigDecimalAmount, transaction.getTax().getTaxAmount());
        assertEquals(GRAPH_QL_ID_FOR_CUSTOMER, transaction.getCustomerId());
        assertEquals(BRAINTREE_CHANNEL, transaction.getChannel());
        assertEquals(MERCHANT_ACCOUNT_ID_FOR_CURRENT_SITE, transaction.getMerchantAccountId());
        assertNotNull(transaction.getVaultPaymentMethodAfterTransacting());
        assertEquals(BrainTreeVaultPaymentMethodCriteria.ON_SUCCESSFUL_TRANSACTION,
                transaction.getVaultPaymentMethodAfterTransacting().getWhen());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
    }

    @Test
    public void shouldCreateVariablesMapWhenChargePayPalAccount() throws IOException {
        //given
        saleRequest.setTaxAmount(bigDecimalAmount);
        saleRequest.setCustomerId(CUSTOMER_ID);
        saleRequest.setUsePaymentMethodToken(false);
        saleRequest.setMerchantAccountIdForCurrentSite(MERCHANT_ACCOUNT_ID_FOR_CURRENT_SITE);
        when(brainTreeCustomerAccountService.getGraphQLIdForCustomer(CUSTOMER_ID)).thenReturn(GRAPH_QL_ID_FOR_CUSTOMER);
        when(brainTreeConfigService.isStoreInVault()).thenReturn(true);
        when(brainTreeConfigService.getBrainTreeChannel()).thenReturn(BRAINTREE_CHANNEL);
        when(brainTreeConfigService.isMultiCaptureEnabled()).thenReturn(true);
        doReturn(mapResult).when(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CHARGE_PAYPAL), any());

        //when
        command.perform(saleRequest);

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

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

        assertNotNull(input);
        BrainTreeTransactionInput transaction = input.getTransaction();
        assertNotNull(transaction);
        assertEquals(bigDecimalAmount, transaction.getAmount());
        assertNotNull(transaction.getTax());
        assertEquals(bigDecimalAmount, transaction.getTax().getTaxAmount());
        assertEquals(GRAPH_QL_ID_FOR_CUSTOMER, transaction.getCustomerId());
        assertEquals(BRAINTREE_CHANNEL, transaction.getChannel());
        assertEquals(MERCHANT_ACCOUNT_ID_FOR_CURRENT_SITE, transaction.getMerchantAccountId());
        assertNotNull(transaction.getVaultPaymentMethodAfterTransacting());
        assertEquals(BrainTreeVaultPaymentMethodCriteria.ON_SUCCESSFUL_TRANSACTION,
                transaction.getVaultPaymentMethodAfterTransacting().getWhen());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_CHARGE_PAYPAL), any());
    }

    @Test
    public void shouldSetCustomFieldsWhenCreateVariablesMap() throws IOException {
        //given
        Map<String, String> customFields = new HashMap<>();
        customFields.put(CUSTOM_FIELD_KEY, CUSTOM_FIELD_VALUE);
        saleRequest.setCustomFields(customFields);
        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);

        //when
        command.perform(saleRequest);

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

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

        assertNotNull(input);
        BrainTreeTransactionInput transaction = input.getTransaction();
        assertNotNull(transaction);
        assertNotNull(transaction.getCustomFields());
        assertFalse(transaction.getCustomFields().isEmpty());
        assertEquals(CUSTOM_FIELD_KEY, transaction.getCustomFields().get(0).getName());
        assertEquals(CUSTOM_FIELD_VALUE, transaction.getCustomFields().get(0).getValue());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
    }

    @Test
    public void shouldSetAdditionalParametersWhenUsePaymentMethodTokenIsTrueWhenCreateVariablesMap() throws IOException {
        //given
        saleRequest.setUsePaymentMethodToken(true);
        saleRequest.setPaymentMethodToken(PAYMENT_METHOD_TOKEN);

        when(paymentInfoService.getGraphQLTokenForPaymentMethod(PAYMENT_METHOD_TOKEN)).thenReturn(GRAPH_QL_TOKEN_FOR_PAYMENT_METHOD);
        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);

        //when
        command.perform(saleRequest);

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

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

        assertNotNull(input);
        assertEquals(GRAPH_QL_TOKEN_FOR_PAYMENT_METHOD, input.getPaymentMethodId());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
    }

    @Test
    public void shouldSetAdditionalParametersWhenUsePaymentMethodTokenIsFalseAndMethodNonceIsBlank() throws IOException {
        //given
        saleRequest.setUsePaymentMethodToken(false);
        saleRequest.setPaymentMethodToken(PAYMENT_METHOD_TOKEN);

        when(paymentInfoService.getGraphQLTokenForPaymentMethod(PAYMENT_METHOD_TOKEN)).thenReturn(GRAPH_QL_TOKEN_FOR_PAYMENT_METHOD);
        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);

        //when
        command.perform(saleRequest);

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

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

        assertNotNull(input);
        assertEquals(GRAPH_QL_TOKEN_FOR_PAYMENT_METHOD, input.getPaymentMethodId());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
    }

    @Test
    public void shouldSetAdditionalParametersWhenUsePaymentMethodTokenIsFalseAndMethodNonceIsNotBlank() throws IOException {
        //given
        saleRequest.setUsePaymentMethodToken(false);
        saleRequest.setMethodNonce(NONCE);

        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);

        //when
        command.perform(saleRequest);

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

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

        assertNotNull(input);
        assertEquals(NONCE, input.getPaymentMethodId());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
    }

    @Test
    public void shouldSetAdditionalParametersWhenUsePaymentMethodTokenIsFalseAndMethodNonceAndPaymentMethodTokenAreBlank() throws IOException {
        //given
        CardInfo cardInfo = new CardInfo();
        cardInfo.setCardHolderFullName(CARD_HOLDER_FULL_NAME);
        cardInfo.setCardNumber(CARD_NUMBER);
        cardInfo.setCv2Number(CV_2_NUMBER);
        cardInfo.setExpirationMonth(EXPIRATION_MONTH);
        cardInfo.setExpirationYear(EXPIRATION_YEAR);

        saleRequest = new BrainTreeSaleTransactionRequest(MERCHANT_TRANSACTION_CODE, cardInfo,
                Currency.getInstance(Locale.US), bigDecimalAmount, billingInfo);

        BrainTreeTokenizeCardResult tokenizeCardResult = new BrainTreeTokenizeCardResult();
        tokenizeCardResult.setMethodGraphQLId(METHOD_GRAPH_QL_ID);

        saleRequest.setUsePaymentMethodToken(false);

        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);
        when(brainTreePaymentService.tokenizeCreditCard(any(BrainTreeTokenizeCreditCardRequest.class))).thenReturn(tokenizeCardResult);

        //when
        command.perform(saleRequest);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), variables.capture());
        verify(brainTreePaymentService, times(2)).tokenizeCreditCard(cardRequestArgumentCaptor.capture());
        assertNotNull(variables.getValue());

        BrainTreeChargePaymentMethodInput input = (BrainTreeChargePaymentMethodInput) variables.getValue().get(INPUT_PARAMETER);
        BrainTreeTokenizeCreditCardRequest cardRequest = cardRequestArgumentCaptor.getValue();

        assertNotNull(input);
        assertEquals(METHOD_GRAPH_QL_ID, input.getPaymentMethodId());
        assertNotNull(cardRequest);
        assertEquals(CARD_HOLDER_FULL_NAME, cardRequest.getCardHolderName());
        assertEquals(CARD_NUMBER, cardRequest.getCardNumber());
        assertEquals(CV_2_NUMBER, cardRequest.getCvv());
        assertEquals(EXPIRATION_MONTH, cardRequest.getExpirationMonth().intValue());
        assertEquals(EXPIRATION_YEAR, cardRequest.getExpirationYear().intValue());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
    }

    @Test
    public void shouldSetTransactionDescriptorInputIfNeededConfigsNotEmpty() throws IOException {
        BrainTreeTransactionDescriptorInput descriptorInput = mock(BrainTreeTransactionDescriptorInput.class);
        //given
        saleRequest.setUsePaymentMethodToken(false);
        saleRequest.setPaymentType(PAYPAL_ACCOUNT_INSTRUMENT_TYPE);

        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);
        when(descriptorService.getTransactionDescriptor(PAYPAL_ACCOUNT_INSTRUMENT_TYPE)).thenReturn(descriptorInput);

        //when
        command.perform(saleRequest);

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

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

        assertNotNull(input);
        assertNotNull(input.getTransaction());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
        verify(descriptorService, times(2)).getTransactionDescriptor(PAYPAL_ACCOUNT_INSTRUMENT_TYPE);
    }

    @Test
    public void shouldAddShippingInfoWhenCreateVariablesMap() throws IOException {
        //given
        billingInfo.setState(STATE);
        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);

        //when
        command.perform(saleRequest);

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

        BrainTreeChargePaymentMethodInput input = (BrainTreeChargePaymentMethodInput) variables.getValue().get(INPUT_PARAMETER);
        assertNotNull(input);
        assertNotNull(input.getTransaction());
        assertNotNull(input.getTransaction().getShipping());

        BrainTreeAddressInput shippingAddressInput = input.getTransaction().getShipping().getShippingAddress();
        assertNotNull(shippingAddressInput);
        assertEquals(STATE, shippingAddressInput.getRegion());
        assertEquals(COUNTRY, shippingAddressInput.getCountryCode());
        assertEquals(FIRST_NAME, shippingAddressInput.getFirstName());
        assertEquals(LAST_NAME, shippingAddressInput.getLastName());
        assertEquals(POSTAL_CODE, shippingAddressInput.getPostalCode());
        assertEquals(CITY, shippingAddressInput.getLocality());

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

    @Test
    public void shouldAddShippingInfoWhenStateIsNullWhenCreateVariablesMap() throws IOException {
        //given
        billingInfo.setRegion(REGION);
        when(objectMapper.convertValue(any(), eq(BrainTreeTransactionPayload.class))).thenReturn(null);

        //when
        command.perform(saleRequest);

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

        BrainTreeChargePaymentMethodInput input = (BrainTreeChargePaymentMethodInput) variables.getValue().get(INPUT_PARAMETER);
        assertNotNull(input);
        assertNotNull(input.getTransaction());
        assertNotNull(input.getTransaction().getShipping());

        BrainTreeAddressInput shippingAddressInput = input.getTransaction().getShipping().getShippingAddress();
        assertNotNull(shippingAddressInput);
        assertEquals(REGION, shippingAddressInput.getRegion());
        assertEquals(COUNTRY, shippingAddressInput.getCountryCode());
        assertEquals(FIRST_NAME, shippingAddressInput.getFirstName());
        assertEquals(LAST_NAME, shippingAddressInput.getLastName());
        assertEquals(POSTAL_CODE, shippingAddressInput.getPostalCode());
        assertEquals(CITY, shippingAddressInput.getLocality());

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

    @Test
    public void shouldAddTransactionCustomerDetailsWhenCustomerIdIsNullWhenCreateVariablesMap() throws IOException {
        //given
        saleRequest.setCustomerEmail(EMAIL);

        //when
        command.perform(saleRequest);

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

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

        assertNotNull(input);
        assertNotNull(input.getTransaction());
        assertNotNull(input.getTransaction().getCustomerDetails());
        assertEquals(EMAIL, input.getTransaction().getCustomerDetails().getEmail());
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
    }

    @Test
    public void shouldTranslateResponseWhenSuccessfulTransaction() throws IOException {
        //given
        BrainTreeMonetaryAmount monetaryAmount = new BrainTreeMonetaryAmount();
        monetaryAmount.setValue(AMOUNT);
        monetaryAmount.setCurrencyCode(USD);

        saleTransaction.setStatus(BrainTreePaymentStatus.SETTLED);
        saleTransaction.setMerchantAccountId(MERCHANT_ACCOUNT_ID);
        saleTransaction.setLegacyId(LEGACY_ID);
        saleTransaction.setId(SALE_TRANSACTION_ID);
        saleTransaction.setAmount(monetaryAmount);
        saleTransaction.setCreatedAt(createdAt);
        saleTransaction.setPaymentMethodSnapshot(paymentMethodSnapshot);

        when(paymentInfoService.getPaymentInstrumentTypeBySnapshotAndOrigin(paymentMethodSnapshot))
                .thenReturn(PAYPAL_ACCOUNT_INSTRUMENT_TYPE);

        //when
        BrainTreeSaleTransactionResult commandResult = command.perform(saleRequest);

        //then
        assertNotNull(commandResult);
        assertTrue(commandResult.isSuccess());
        assertEquals(MERCHANT_ACCOUNT_ID, commandResult.getMerchantTransactionCode());
        assertEquals(LEGACY_ID, commandResult.getRequestId());
        assertEquals(SALE_TRANSACTION_ID, commandResult.getTransactionGraphQLId());
        assertEquals(LEGACY_ID, commandResult.getTransactionId());
        assertEquals(LEGACY_ID, commandResult.getRequestToken());
        assertEquals(TransactionStatusDetails.SUCCESFULL, commandResult.getTransactionStatusDetails());
        assertEquals(TransactionStatus.ACCEPTED, commandResult.getTransactionStatus());
        assertEquals(AMOUNT, commandResult.getAmount().toString());
        assertEquals(USD, commandResult.getCurrencyIsoCode());
        assertNotNull(commandResult.getCreatedAt());
        assertEquals(PAYPAL_ACCOUNT_INSTRUMENT_TYPE, commandResult.getPaymentInstrumentType());
        assertNotNull(commandResult.getRequestBody());
        assertNotNull(commandResult.getResponseBody());

        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
        verify(paymentInfoService).getPaymentInstrumentTypeBySnapshotAndOrigin(paymentMethodSnapshot);
    }

    @Test
    public void shouldTranslateResponseWhenUnsuccessfulTransaction() 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
        BrainTreeSaleTransactionResult commandResult = command.perform(saleRequest);

        //then
        assertNotNull(commandResult);
        assertFalse(commandResult.isSuccess());
        assertEquals(LEGACY_CODE, commandResult.getErrorCode());
        assertEquals(ERROR_MESSAGE, commandResult.getErrorMessage());

        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), 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(saleRequest);

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

    @Test
    public void shouldTranslateResponseWhenTransactionPayloadIsNull() throws IOException {
        //given
        when(objectMapper.convertValue(transactionPayload, BrainTreeTransactionPayload.class))
                .thenReturn(null);

        //when
        BrainTreeSaleTransactionResult commandResult = command.perform(saleRequest);

        //then
        assertNotNull(commandResult);
        assertFalse(commandResult.isSuccess());
        assertNull(commandResult.getStatus());
        assertNull(commandResult.getMerchantTransactionCode());
        assertNull(commandResult.getRequestId());
        assertNull(commandResult.getTransactionId());
        assertNull(commandResult.getTransactionGraphQLId());
        assertNull(commandResult.getRequestToken());
        assertNull(commandResult.getTransactionStatus());
        assertNull(commandResult.getTransactionStatusDetails());
        assertNull(commandResult.getAmount());
        assertNull(commandResult.getAmount());
        assertNull(commandResult.getCurrencyIsoCode());
        assertNull(commandResult.getCreatedAt());
        assertNull(commandResult.getCaptureId());
        assertNotNull(commandResult.getRequestBody());
        assertNotNull(commandResult.getResponseBody());

        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
        verify(paymentInfoService, never()).getPaymentInstrumentTypeBySnapshotAndOrigin(any(BrainTreePaymentMethodSnapshot.class));
    }

    @Test
    public void shouldTranslateResponseWhenSaleTransactionIsNull() throws IOException {
        //given
        transactionPayload.setTransaction(null);

        //when
        BrainTreeSaleTransactionResult commandResult = command.perform(saleRequest);

        //then
        assertNotNull(commandResult);
        assertFalse(commandResult.isSuccess());
        assertNull(commandResult.getStatus());
        assertNull(commandResult.getMerchantTransactionCode());
        assertNull(commandResult.getRequestId());
        assertNull(commandResult.getTransactionId());
        assertNull(commandResult.getTransactionGraphQLId());
        assertNull(commandResult.getRequestToken());
        assertNull(commandResult.getTransactionStatus());
        assertNull(commandResult.getTransactionStatusDetails());
        assertNull(commandResult.getAmount());
        assertNull(commandResult.getAmount());
        assertNull(commandResult.getCurrencyIsoCode());
        assertNull(commandResult.getCreatedAt());
        assertNull(commandResult.getCaptureId());
        assertNotNull(commandResult.getRequestBody());
        assertNotNull(commandResult.getResponseBody());

        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
        verify(paymentInfoService, never()).getPaymentInstrumentTypeBySnapshotAndOrigin(any(BrainTreePaymentMethodSnapshot.class));
    }

    @Test
    public void shouldTranslateResponseWhenTransactionStatusIsNull() throws IOException {
        //when
        BrainTreeSaleTransactionResult commandResult = command.perform(saleRequest);

        //then
        assertNotNull(commandResult);
        assertFalse(commandResult.isSuccess());
        assertNull(commandResult.getStatus());
        assertNull(commandResult.getMerchantTransactionCode());
        assertNull(commandResult.getRequestId());
        assertNull(commandResult.getTransactionId());
        assertNull(commandResult.getTransactionGraphQLId());
        assertNull(commandResult.getRequestToken());
        assertNull(commandResult.getTransactionStatus());
        assertNull(commandResult.getTransactionStatusDetails());
        assertNull(commandResult.getAmount());
        assertNull(commandResult.getAmount());
        assertNull(commandResult.getCurrencyIsoCode());
        assertNull(commandResult.getCreatedAt());
        assertNull(commandResult.getCaptureId());
        assertNotNull(commandResult.getRequestBody());
        assertNotNull(commandResult.getResponseBody());

        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME_AUTHORIZATION_PAYPAL), any());
        verify(paymentInfoService, never()).getPaymentInstrumentTypeBySnapshotAndOrigin(any(BrainTreePaymentMethodSnapshot.class));
    }

}
