package com.pivotallabs.api;

import com.pivotallabs.util.Strings;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AuthScope;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.ClientContext;
import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.tester.org.apache.http.HttpRequestInfo;

import java.io.InputStream;
import java.util.Map;

import static com.pivotallabs.TestResponses.GENERIC_XML;
import static com.pivotallabs.util.TestUtil.asString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;

@RunWith(RobolectricTestRunner.class)
public class ApiGatewayTest {
    private ApiGateway apiGateway;
    private TestApiResponseCallbacks responseCallbacks;

    @Before
    public void setUp() throws Exception {
        apiGateway = new ApiGateway();
        responseCallbacks = new TestApiResponseCallbacks();
    }

    @Test
    public void dispatch_shouldCallOntoTheSuccessWhenApiResponseIsSuccess() throws Exception {
        XmlApiResponse apiResponse = new XmlApiResponse(200);
        apiGateway.dispatch(apiResponse, responseCallbacks);

        assertThat(responseCallbacks.successResponse, sameInstance(apiResponse));
        assertThat(responseCallbacks.failureResponse, nullValue());
        assertThat(responseCallbacks.onCompleteWasCalled, equalTo(true));
    }

    @Test
    public void dispatch_shouldCallOnFailureWhenApiResponseIsFailure() throws Exception {
        XmlApiResponse apiResponse = new XmlApiResponse(500);
        apiGateway.dispatch(apiResponse, responseCallbacks);

        assertThat(responseCallbacks.failureResponse, sameInstance((ApiResponse) apiResponse));
        assertThat(responseCallbacks.successResponse, nullValue());
        assertThat(responseCallbacks.onCompleteWasCalled, equalTo(true));
    }

    @Test
    public void dispatch_shouldCallOnFailureWhenXmlParseErrorOccurs() throws Exception {
        Robolectric.addPendingHttpResponse(200, "Invalid x-shmail");
        apiGateway.makeRequest(new TestGetRequest(), responseCallbacks);
        assertThat(responseCallbacks.successResponse, nullValue());
        assertThat(responseCallbacks.failureResponse, not(nullValue()));
        assertThat(responseCallbacks.onCompleteWasCalled, equalTo(true));
    }

    @Test
    public void dispatch_shouldCallOnFailureWhenOnSuccessFails() throws Exception {
        XmlApiResponse apiResponse = new XmlApiResponse(200);

        TestApiResponseCallbacks callbacks = new TestApiResponseCallbacks() {
            @Override
            public void onSuccess(XmlApiResponse successResponse) {
                throw new RuntimeException("boom!");
            }
        };
        apiGateway.dispatch(apiResponse, callbacks);
        assertThat(callbacks.failureResponse, sameInstance((ApiResponse) apiResponse));
        assertThat(callbacks.onCompleteWasCalled, equalTo(true));
    }

    @Test
    public void shouldMakeRemoteGetCalls() {
        Robolectric.getBackgroundScheduler().pause();

        TestGetRequest apiRequest = new TestGetRequest();
        apiGateway.makeRequest(apiRequest, responseCallbacks);

        Robolectric.addPendingHttpResponse(200, GENERIC_XML);

        Robolectric.getBackgroundScheduler().runOneTask();

        HttpRequestInfo sentHttpRequestData = Robolectric.getSentHttpRequestInfo(0);
        HttpRequest sentHttpRequest = sentHttpRequestData.getHttpRequest();
        assertThat(sentHttpRequest.getRequestLine().getUri(), equalTo("www.example.com"));
        assertThat(sentHttpRequest.getRequestLine().getMethod(), equalTo(HttpGet.METHOD_NAME));

        assertThat(sentHttpRequest.getHeaders("foo")[0].getValue(), equalTo("bar"));

        CredentialsProvider credentialsProvider =
                (CredentialsProvider) sentHttpRequestData.getHttpContext().getAttribute(ClientContext.CREDS_PROVIDER);
        assertThat(credentialsProvider.getCredentials(AuthScope.ANY).getUserPrincipal().getName(), CoreMatchers.equalTo("spongebob"));
        assertThat(credentialsProvider.getCredentials(AuthScope.ANY).getPassword(), CoreMatchers.equalTo("squarepants"));
    }

    @Test
    public void shouldMakeRemotePostCalls() throws Exception {
        Robolectric.getBackgroundScheduler().pause();

        TestPostRequest apiRequest = new TestPostRequest();
        apiGateway.makeRequest(apiRequest, responseCallbacks);

        Robolectric.addPendingHttpResponse(200, GENERIC_XML);

        Robolectric.getBackgroundScheduler().runOneTask();

        HttpRequestInfo sentHttpRequestData = Robolectric.getSentHttpRequestInfo(0);
        HttpRequest sentHttpRequest = sentHttpRequestData.getHttpRequest();
        assertThat(sentHttpRequest.getRequestLine().getUri(), equalTo("www.example.com"));
        assertThat(sentHttpRequest.getRequestLine().getMethod(), equalTo(HttpPost.METHOD_NAME));

        assertThat(sentHttpRequest.getHeaders("foo")[0].getValue(), equalTo("bar"));

        InputStream contentStream = ((HttpPost) sentHttpRequest).getEntity().getContent();

        assertThat(Strings.fromStream(contentStream), CoreMatchers.equalTo("post body content"));

        CredentialsProvider credentialsProvider =
                (CredentialsProvider) sentHttpRequestData.getHttpContext().getAttribute(ClientContext.CREDS_PROVIDER);
        assertThat(credentialsProvider.getCredentials(AuthScope.ANY).getUserPrincipal().getName(), CoreMatchers.equalTo("spongebob"));
        assertThat(credentialsProvider.getCredentials(AuthScope.ANY).getPassword(), CoreMatchers.equalTo("squarepants"));
    }

    @Test
    public void shouldDispatchUponReceivingResponse() throws Exception {
        Robolectric.getBackgroundScheduler().pause();
        Robolectric.getUiThreadScheduler().pause();

        Robolectric.addPendingHttpResponse(200, GENERIC_XML);

        apiGateway.makeRequest(new TestGetRequest(), responseCallbacks);
        Robolectric.getBackgroundScheduler().runOneTask();

        assertThat(responseCallbacks.successResponse, nullValue());

        Robolectric.getUiThreadScheduler().runOneTask();

        assertThat(asString(responseCallbacks.successResponse.getResponseDocument()), equalTo(GENERIC_XML));
    }

    private class TestGetRequest extends ApiRequest<XmlApiResponse> {
        @Override
        public String getUrlString() {
            return "www.example.com";
        }

        @Override
        public Map<String, String> getParameters() {
            Map<String, String> parameters = super.getParameters();
            parameters.put("baz", "bang");
            return parameters;
        }

        @Override
        public Map<String, String> getHeaders() {
            Map<String, String> headers = super.getHeaders();
            headers.put("foo", "bar");
            return headers;
        }

        @Override
        public String getMethod() {
            return HttpGet.METHOD_NAME;
        }

        @Override
        public String getPostBody() {
            return super.getPostBody();
        }

        @Override
        public String getUsername() {
            return "spongebob";
        }

        @Override
        public String getPassword() {
            return "squarepants";
        }

        @Override
        public XmlApiResponse createResponse(int statusCode) {
            return new XmlApiResponse(statusCode);
        }
    }

    private class TestPostRequest extends ApiRequest {
        @Override
        public String getUrlString() {
            return "www.example.com";
        }

        @Override
        public Map<String, String> getParameters() {
            Map<String, String> parameters = super.getParameters();
            parameters.put("baz", "bang");
            return parameters;
        }

        @Override
        public Map<String, String> getHeaders() {
            Map<String, String> headers = super.getHeaders();
            headers.put("foo", "bar");
            return headers;
        }

        @Override
        public String getMethod() {
            return HttpPost.METHOD_NAME;
        }

        @Override
        public String getPostBody() {
            return "post body content";
        }

        @Override
        public String getUsername() {
            return "spongebob";
        }

        @Override
        public String getPassword() {
            return "squarepants";
        }

        @Override
        public ApiResponse createResponse(int statusCode) {
            return new XmlApiResponse(statusCode);
        }
    }
}