/*
 * Copyright (c) 2016 HERE Europe B.V.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.here.account.http.java;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.here.account.auth.NoAuthorizer;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import com.here.account.auth.OAuth2Authorizer;
import com.here.account.http.HttpConstants;
import com.here.account.http.HttpException;
import com.here.account.http.HttpProvider;
import com.here.account.http.HttpProvider.HttpRequest;
import com.here.account.http.HttpProvider.HttpRequestAuthorizer;
import com.here.account.http.HttpProvider.HttpResponse;
import com.here.account.http.apache.ApacheHttpClientProvider;
import com.here.account.util.JsonSerializer;

public class JavaHttpProviderTest {
    
    private final String urlString = "http://www.example.com/";
    private String method = "GET";
    private Map<String, List<String>> formParams;
    private String jsonBody;
    boolean callGetRequest = false;
    private HttpURLConnection mockHttpUrlConnection;

    @Before
    public void setUp() throws IOException {
    }
    
    @Test
    public void test_example() throws HttpException, IOException {
        doRequest();
    }

    @Test
    public void test_additionalHeaders() throws HttpException, IOException {
        addAdditionalHeaders = true;
        doRequest();

        String expectedValue = additionalHeaderValue;
        String value = this.requestHeaders.get(additionalHeaderName);
        assertTrue("expected additional requestHeader to have been set on HtttpURLConnection: "
                + expectedValue + ", actual " + value,
                expectedValue.equals(value));
    }

    @Test
    public void test_example_callGetRequest() throws HttpException, IOException {
        callGetRequest = true;
        jsonBody = "foo";
        
        doRequest();
    }

    
    String expectedContentType;
    
    @Test
    public void test_post_formBody() throws HttpException, IOException {
        method = "POST";
        formParams = new HashMap<String, List<String>>();
        formParams.put("foo", Collections.singletonList("bar"));
        doRequest();
        byte[] body = byteArrayOutputStream.toByteArray();
        byte[] expectedBody = "foo=bar".getBytes(JsonSerializer.CHARSET);
        verifyBytes(body, expectedBody);

        expectedContentType = "application/x-www-form-urlencoded";
        verifyContentType();
    }
    
    private void verifyContentType() {
        String actualContentType = requestHeaders.get("Content-Type");
        assertTrue("expectedContentType "+expectedContentType+" !.equal to actualContentType "+actualContentType, 
                expectedContentType.equals(actualContentType));
    }
    
    @Test
    public void test_put_json() throws HttpException, IOException {
        method = "PUT";
        jsonBody = "{\"foo\":\"bar\"}";
        doRequest();
        byte[] body = byteArrayOutputStream.toByteArray();
        byte[] expectedBody = jsonBody.getBytes(JsonSerializer.CHARSET);
        verifyBytes(body, expectedBody);
        
        expectedContentType = "application/json";
        verifyContentType();

    }
    
    protected boolean bytesMatch(byte[] actualBody, byte[] expectedBody) {
        try {
            verifyBytes(actualBody, expectedBody);
            return true;
        } catch (AssertionError e) {
            return false;
        }
    }
    
    protected void verifyBytes(byte[] actualBody, byte[] expectedBody) {
        assertTrue("nullness didn't match", !(null != actualBody ^ null != expectedBody));
        if (null != actualBody) {
            int actualLength = actualBody.length;
            int expectedLength = expectedBody.length;
            assertTrue("expected length "+expectedLength+", actual length "+actualLength, expectedLength == actualLength);
            for (int i = 0; i < actualLength; i++) {
                byte actualByte = actualBody[i];
                byte expectedByte = expectedBody[i];
                assertTrue("expected byte "+expectedByte+", actual byte "+actualByte, expectedByte == actualByte);
            }
        }
    }

    @Test(expected = IllegalArgumentException.class) 
    public void test_wrongRequestClass() throws HttpException, IOException {
        JavaHttpProvider javaHttpProvider = (JavaHttpProvider) JavaHttpProvider.builder().build();
        HttpRequest httpRequest = new HttpRequest() {

            @Override
            public void addAuthorizationHeader(String value) {
                // no-op
            }

            @Override
            public void addHeader(String name, String value) {
                // no-op
            }

        };
        javaHttpProvider.execute(httpRequest);
    }
    
    HttpRequestAuthorizer httpRequestAuthorizer = new OAuth2Authorizer("my-accessToken");

    boolean addAdditionalHeaders = false;
    String additionalHeaderName = "foo";
    String additionalHeaderValue = "bar";

    protected void doRequest() throws MalformedURLException, IOException, HttpException {
        JavaHttpProvider javaHttpProvider = (JavaHttpProvider) JavaHttpProvider.builder().build();
        JavaHttpProvider mock = Mockito.spy(javaHttpProvider);
        mockHttpUrlConnection = getMockHttpUrlConnection();
        Mockito.doReturn(mockHttpUrlConnection).when(mock).getHttpUrlConnection(Mockito.anyString());

        HttpProvider.HttpRequest httpRequest;
        if (callGetRequest) {
            httpRequest = mock.getRequest(httpRequestAuthorizer, method, urlString, jsonBody);
        } else {
            if (null != jsonBody) {
                httpRequest = javaHttpProvider.getRequest(httpRequestAuthorizer, method, urlString, jsonBody);
            } else {
                httpRequest = javaHttpProvider.getRequest(httpRequestAuthorizer, method, urlString, formParams);
            }
        }
        if (addAdditionalHeaders) {
            httpRequest.addHeader(additionalHeaderName, additionalHeaderValue);
        }

        HttpResponse httpResponse = mock.execute(httpRequest);
        assertTrue("httpResponse was null", null != httpResponse);
    }
    
    @Test
    public void test_getFormBody_keyOnly() throws UnsupportedEncodingException {
        Map<String, List<String>> formParams = new HashMap<String, List<String>>();
        List<String> valueList= new ArrayList<String>();
        formParams.put("ant", valueList);
        formParams.put("bar", null);

        byte[] formBody = JavaHttpProvider.getFormBody(formParams);
        String expectedOption1 = "ant&bar";
        String expectedOption2 = "bar&ant";
        byte[] expectedBodyOption1 = expectedOption1.getBytes("UTF-8");
        byte[] expectedBodyOption2 = expectedOption2.getBytes("UTF-8");
        assertTrue("formBody "+formBody+" didn't match one of expected "+expectedBodyOption1+" or "+expectedBodyOption2, 
                bytesMatch(formBody, expectedBodyOption1) || bytesMatch(formBody, expectedBodyOption2));
    }
    
    @Test
    public void test_getFormBody_second() throws UnsupportedEncodingException {
        Map<String, List<String>> formParams = new HashMap<String, List<String>>();
        List<String> valueList= Collections.singletonList("bar");
        formParams.put("foo", valueList);
        List<String> valueList2 = Collections.singletonList("dog");
        formParams.put("ant", valueList2);
        byte[] formBody = JavaHttpProvider.getFormBody(formParams);
        String expectedOption1 = "foo=bar&ant=dog";
        String expectedOption2 = "ant=dog&foo=bar";
        byte[] expectedBodyOption1 = expectedOption1.getBytes("UTF-8");
        byte[] expectedBodyOption2 = expectedOption2.getBytes("UTF-8");
        assertTrue("formBody "+formBody+" didn't match one of expected "+expectedBodyOption1+" or "+expectedBodyOption2, 
                bytesMatch(formBody, expectedBodyOption1) || bytesMatch(formBody, expectedBodyOption2));
    }
    
    @Test
    public void test_getFormBody_second2() throws UnsupportedEncodingException {
        Map<String, List<String>> formParams = new HashMap<String, List<String>>();
        List<String> valueList= Collections.singletonList("bar");
        formParams.put("foo", valueList);
        List<String> valueList2 = new ArrayList<String>();
        valueList2.add("dog");
        valueList2.add("cat");
        formParams.put("ant", valueList2);
        byte[] formBody = JavaHttpProvider.getFormBody(formParams);
        String expectedOption1 = "foo=bar&ant=dog&ant=cat";
        String expectedOption2 = "foo=bar&ant=cat&ant=dog";
        String expectedOption3 = "ant=dog&foo=bar&ant=cat";
        String expectedOption4 = "ant=dog&ant=cat&foo=bar";
        String expectedOption5 = "ant=cat&foo=bar&ant=dog";
        String expectedOption6 = "ant=cat&ant=dog&foo=bar";
        assertTrue("formBody "+formBody+" didn't match one of expected",
                bytesMatch(formBody, expectedOption1.getBytes("UTF-8")) 
                || bytesMatch(formBody, expectedOption2.getBytes("UTF-8"))
                || bytesMatch(formBody, expectedOption3.getBytes("UTF-8"))
                || bytesMatch(formBody, expectedOption4.getBytes("UTF-8"))
                || bytesMatch(formBody, expectedOption5.getBytes("UTF-8"))
                || bytesMatch(formBody, expectedOption6.getBytes("UTF-8"))
                );
    }

    
    @Test
    public void test_404() throws HttpException, IOException {
        statusCode = 404;
        doRequest();
    }
    
    @Test
    public void test_authorizationHeader() throws HttpException, IOException {
        doRequest();
    }
    @Test
    public void test_put() throws HttpException, IOException {
        method = "PUT";
        doRequest();
    }
    
    @Test
    public void test_openConnection() throws IOException {
        JavaHttpProvider javaHttpProvider = (JavaHttpProvider) JavaHttpProvider.builder().build();
        String urlString = "http://localhost:8080/foo";
        HttpURLConnection httpUrlConnection = javaHttpProvider.getHttpUrlConnection(urlString);
        // not actually connected, unless you call .open
        assertTrue("httpUrlConnection was null", null != httpUrlConnection);
    }
    
    @Test
    public void test_noAuthorizationHeader() throws IOException, HttpException {
        httpRequestAuthorizer = new NoAuthorizer();

        doRequest();
    }

    @Test
    public void test_increaseCodeCoverageInJavaHttpProvider_getRequest() {
        httpRequestAuthorizer = new OAuth2Authorizer("my-accessToken");
        method = "GET";
        JavaHttpProvider javaHttpProvider = (JavaHttpProvider) JavaHttpProvider.builder().build();
        HttpRequest httpRequest = javaHttpProvider.getRequest(httpRequestAuthorizer, method, urlString, (String)null);
        assertNotNull(httpRequest);
    }

    int statusCode = 200;
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    Map<String, String> requestHeaders = new HashMap<String, String>();

    private HttpURLConnection getMockHttpUrlConnection() throws MalformedURLException {
        URL u = new URL(urlString);
        return new HttpURLConnection( u) {
            
            @Override
            public int getResponseCode() {
                return statusCode;
            }

            @Override
            public void disconnect() {
                // TODO Auto-generated method stub
                
            }

            @Override
            public boolean usingProxy() {
                // TODO Auto-generated method stub
                return false;
            }

            @Override
            public void connect() throws IOException {
                // TODO Auto-generated method stub
                
            }
            
            @Override
            public void setRequestProperty(String headerName, String headerValue) {
                super.setRequestProperty(headerName, headerValue);
                requestHeaders.put(headerName, headerValue);
            }

            @Override
            public String getHeaderField(String headerFieldName) {
                if (HttpConstants.CONTENT_LENGTH_HEADER.equals(headerFieldName)) {
                    return "" + urlString.getBytes(JsonSerializer.CHARSET).length;
                }
                return null;
            }
            
            @Override
            public OutputStream getOutputStream() throws IOException {
                // wrap the output capturing stream in this.
                return new OutputStream() {

                    @Override
                    public void write(int b) throws IOException {
                        byteArrayOutputStream.write(b);
                    }
                    
                };
            }
            
            @Override
            public InputStream getInputStream() throws IOException {
                return new ByteArrayInputStream(urlString.getBytes(JsonSerializer.CHARSET));
            }
            
        };
    }
    
    @Test
    public void test_JavaHttpClientResponse_Headers() throws HttpException, IOException {
        String requestBodyJson = "{\"foo\":\"bar\"}";
        String url = "http://google.com";
        HttpProvider httpProvider = JavaHttpProvider.builder().build();
        HttpRequest httpRequest = httpProvider.getRequest(httpRequestAuthorizer, "PUT", url, requestBodyJson);
        HttpProvider.HttpResponse response = httpProvider.execute(httpRequest);
        assertEquals(HttpURLConnection.HTTP_BAD_METHOD, response.getStatusCode());
        assertNotNull("response body is null", response.getResponseBody());
        assertTrue("response content length is 0", 0<response.getContentLength());;
        assertTrue("Content-Type Header should be present", response.getHeaders().get("Content-Type") != null);
    }

}