package com.braintree.graphql.commands.impl;

import com.braintree.command.result.BrainTreeSubscriptionResult;
import com.braintree.commands.impl.DefaultBrainTreeLoggingHandler;
import com.braintree.hybris.data.graphql.commands.request.BrainTreeCreateCustomerInput;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeCreateCustomerPayload;
import com.braintree.hybris.data.graphql.commands.response.BrainTreeCustomer;
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 de.hybris.platform.payment.commands.request.CreateSubscriptionRequest;
import de.hybris.platform.payment.dto.BillingInfo;
import de.hybris.platform.payment.dto.TransactionStatus;
import de.hybris.platform.payment.dto.TransactionStatusDetails;
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.Mockito.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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

    private static final String EMAIL = "email";
    private static final String FIRST_NAME = "Ivan";
    private static final String LAST_NAME = "Kozak";
    private static final String DATA_MAP_KEY = "data";
    private static final String DEFINITION_FILE_NAME = "createCustomer";
    private static final String INPUT_PARAMETER = "input";
    private static final String LEGACY_ID = "LegacyId";
    private static final String CUSTOMER_ID = "customerId";
    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";

    @Mock
    private ObjectMapper objectMapper;

    @Mock
    private DefaultBrainTreeLoggingHandler loggingHandler;

    @Spy
    @InjectMocks
    private DefaultBrainTreeGraphQLCreateSubscriptionCommand command;

    @Mock
    private CreateSubscriptionRequest createSubscriptionRequest;

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

    private Map<String, Object> mapResult;

    @Before
    public void setUp() throws IOException {
        BillingInfo billingInfo = new BillingInfo();
        billingInfo.setEmail(EMAIL);
        billingInfo.setFirstName(FIRST_NAME);
        billingInfo.setLastName(LAST_NAME);

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

        when(createSubscriptionRequest.getBillingInfo()).thenReturn(billingInfo);
        doReturn(mapResult).when(command).makeGraphQlCall(any(), any());
    }

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

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

        BrainTreeCreateCustomerInput inputResult = (BrainTreeCreateCustomerInput) variables.getValue().get(INPUT_PARAMETER);
        assertNotNull(inputResult);
        assertNotNull(inputResult.getCustomer());
        assertEquals(EMAIL, inputResult.getCustomer().getEmail());
        assertEquals(FIRST_NAME, inputResult.getCustomer().getFirstName());
        assertEquals(LAST_NAME, inputResult.getCustomer().getLastName());
    }

    @Test
    public void shouldTranslateResponseWhenSuccessfulTransaction() throws IOException {
        //given
        BrainTreeCustomer customer = new BrainTreeCustomer();
        customer.setLegacyId(LEGACY_ID);
        customer.setId(CUSTOMER_ID);

        BrainTreeCreateCustomerPayload payload = new BrainTreeCreateCustomerPayload();
        payload.setCustomer(customer);

        when(objectMapper.convertValue(any(), eq(BrainTreeCreateCustomerPayload.class))).thenReturn(payload);
        doNothing().when(loggingHandler).handleResult(any(BrainTreeCustomer.class));

        //when
        BrainTreeSubscriptionResult result = command.perform(createSubscriptionRequest);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        assertNotNull(result);
        assertEquals(LEGACY_ID, result.getMerchantTransactionCode());
        assertEquals(LEGACY_ID, result.getRequestId());
        assertEquals(CUSTOMER_ID, result.getGraphQLId());
        assertEquals(TransactionStatus.ACCEPTED, result.getTransactionStatus());
        assertEquals(TransactionStatusDetails.SUCCESFULL, result.getTransactionStatusDetails());
    }

    @Test
    public void shouldTranslateResponseWhenCustomerIsNull() throws IOException {
        //given
        BrainTreeCreateCustomerPayload payload = new BrainTreeCreateCustomerPayload();

        when(objectMapper.convertValue(any(), eq(BrainTreeCreateCustomerPayload.class))).thenReturn(payload);

        //when
        BrainTreeSubscriptionResult result = command.perform(createSubscriptionRequest);

        //then
        verify(command).makeGraphQlCall(eq(DEFINITION_FILE_NAME), any());
        assertNotNull(result);
        assertNull(result.getMerchantTransactionCode());
        assertNull(result.getRequestId());
        assertNull(result.getGraphQLId());
        assertEquals(TransactionStatus.REJECTED, result.getTransactionStatus());
        assertNull(result.getTransactionStatusDetails());
    }

    @Test(expected = AdapterException.class)
    public void shouldThrowExceptionWhenGraphQLResponseContainsError() 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.makeGraphQlCall(eq(DEFINITION_FILE_NAME), any())).thenReturn(mapResult);

        //when
        command.perform(createSubscriptionRequest);

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

}
