package com.braintree.graphql;

import com.braintreegateway.Configuration;
import com.braintreegateway.exceptions.UnexpectedException;
import com.braintreegateway.exceptions.AuthenticationException;
import com.braintreegateway.exceptions.AuthorizationException;
import com.braintreegateway.exceptions.NotFoundException;
import com.braintreegateway.exceptions.UpgradeRequiredException;
import com.braintreegateway.exceptions.TooManyRequestsException;
import com.braintreegateway.exceptions.ServerException;
import com.braintreegateway.exceptions.ServiceUnavailableException;
import com.braintreegateway.util.GraphQLClient;
import de.hybris.bootstrap.annotations.UnitTest;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.anyMap;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.any;


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

    private static final String DEFINITION = "DEFINITION";
    private static final String KEY_1 = "KEY_1";
    private static final String KEY_2 = "KEY_2";
    private static final String VALUE_1 = "VALUE_1";
    private static final String VALUE_2 = "VALUE_2";
    private static final String JSON_STRING = "{\"" + KEY_1 + "\":\"" + VALUE_1 + "\",\"" + KEY_2 + "\":\"" + VALUE_2 + "\"}";

    private static final String ERROR_OBJECT_KEY = "errors";
    private static final String ERROR_EXTENSIONS_KEY = "extensions";
    private static final String ERROR_CLASS_KEY = "errorClass";
    private static final String JSON_STRING_ERROR_FIRST_PART = "{\"errors\": [{\"message\":\"Error_message\",\"extensions\":{\"errorClass\":\"";
    private static final String JSON_STRING_ERROR_SECOND_PART = "\"}}]}";

    @Mock
    private Map<String, Object> variables;
    @Mock
    private HttpURLConnection connection;

    @Mock
    private Configuration configuration;

    @Spy
    @InjectMocks
    private BrainTreeGraphQLClient unit;

    @Test
    public void queryTest() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(JSON_STRING).when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        final Map<String, Object> result = unit.query(DEFINITION, variables);

        assertEquals(VALUE_1, result.get(KEY_1));
        assertEquals(VALUE_2, result.get(KEY_2));
    }

    @Test(expected = UnexpectedException.class)
    public void shouldThrowUnexpectedExceptionForConnection() throws IOException {
        doThrow(new IOException()).when(unit).getBuildConnection(anyMap());

        unit.query(DEFINITION, variables);
    }

    @Test(expected = UnexpectedException.class)
    public void shouldThrowUnexpectedExceptionForMapper() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(StringUtils.EMPTY).when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        unit.query(DEFINITION, variables);
    }

    @Test
    public void shouldTestErrorClassValidation() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(JSON_STRING_ERROR_FIRST_PART + GraphQLClient.ErrorClass.VALIDATION.toString() + JSON_STRING_ERROR_SECOND_PART)
                .when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        final Map<String, Object> result = unit.query(DEFINITION, variables);

        final List<Map<String, Object>> errors = (List<Map<String, Object>>) result.get(ERROR_OBJECT_KEY);
        final Map<String, Object> error = errors.get(0);
        final Map<String, Object> extensions = (Map) error.get(ERROR_EXTENSIONS_KEY);
        assertEquals(GraphQLClient.ErrorClass.VALIDATION.toString(), extensions.get(ERROR_CLASS_KEY));
    }

    @Test(expected = AuthenticationException.class)
    public void shouldThrowAuthenticationException() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(JSON_STRING_ERROR_FIRST_PART + GraphQLClient.ErrorClass.AUTHENTICATION.toString() + JSON_STRING_ERROR_SECOND_PART)
                .when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        unit.query(DEFINITION, variables);
    }

    @Test(expected = AuthorizationException.class)
    public void shouldThrowAuthorizationException() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(JSON_STRING_ERROR_FIRST_PART + GraphQLClient.ErrorClass.AUTHORIZATION.toString() + JSON_STRING_ERROR_SECOND_PART)
                .when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        unit.query(DEFINITION, variables);
    }

    @Test(expected = NotFoundException.class)
    public void shouldThrowNotFoundException() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(JSON_STRING_ERROR_FIRST_PART + GraphQLClient.ErrorClass.NOT_FOUND.toString() + JSON_STRING_ERROR_SECOND_PART)
                .when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        unit.query(DEFINITION, variables);
    }

    @Test(expected = UpgradeRequiredException.class)
    public void shouldThrowUpgradeRequiredException() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(JSON_STRING_ERROR_FIRST_PART + GraphQLClient.ErrorClass.UNSUPPORTED_CLIENT.toString() + JSON_STRING_ERROR_SECOND_PART)
                .when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        unit.query(DEFINITION, variables);
    }

    @Test(expected = TooManyRequestsException.class)
    public void shouldThrowTooManyRequestsException() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(JSON_STRING_ERROR_FIRST_PART + GraphQLClient.ErrorClass.RESOURCE_LIMIT.toString() + JSON_STRING_ERROR_SECOND_PART)
                .when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        unit.query(DEFINITION, variables);
    }

    @Test(expected = ServerException.class)
    public void shouldThrowServerException() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(JSON_STRING_ERROR_FIRST_PART + GraphQLClient.ErrorClass.INTERNAL.toString() + JSON_STRING_ERROR_SECOND_PART)
                .when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        unit.query(DEFINITION, variables);
    }

    @Test(expected = ServiceUnavailableException.class)
    public void shouldThrowServiceUnavailableException() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(JSON_STRING_ERROR_FIRST_PART + GraphQLClient.ErrorClass.SERVICE_AVAILABILITY.toString() + JSON_STRING_ERROR_SECOND_PART)
                .when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        unit.query(DEFINITION, variables);
    }

    @Test(expected = UnexpectedException.class)
    public void shouldThrowUnexpectedException() throws IOException {
        doReturn(connection).when(unit).getBuildConnection(anyMap());
        doReturn(JSON_STRING_ERROR_FIRST_PART + GraphQLClient.ErrorClass.UNKNOWN.toString() + JSON_STRING_ERROR_SECOND_PART)
                .when(unit).getHttpDo(anyString(), any(HttpURLConnection.class), anyMap());

        unit.query(DEFINITION, variables);
    }

}
