/*
 * 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.google.api.client.googleapis.services;

import com.google.api.client.googleapis.GoogleUtils;
import com.google.api.client.googleapis.services.AbstractGoogleClientRequest.ApiClientVersion;
import com.google.api.client.googleapis.testing.services.MockGoogleClient;
import com.google.api.client.googleapis.testing.services.MockGoogleClientRequest;
import com.google.api.client.http.EmptyContent;
import com.google.api.client.http.HttpMethods;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.LowLevelHttpRequest;
import com.google.api.client.http.LowLevelHttpResponse;
import com.google.api.client.json.Json;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.api.client.util.StringUtils;
import com.google.common.io.BaseEncoding;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import junit.framework.TestCase;

/**
 * Tests {@link AbstractGoogleClientRequest}.
 *
 * @author Yaniv Inbar
 */
public class AbstractGoogleClientRequestTest extends TestCase {

  private static final String ROOT_URL = "https://www.googleapis.com/test/";
  private static final String SERVICE_PATH = "path/v1/";
  private static final String URI_TEMPLATE = "tests/{testId}";
  private static final JsonFactory JSON_FACTORY = new JacksonFactory();
  private static final JsonObjectParser JSON_OBJECT_PARSER = new JsonObjectParser(JSON_FACTORY);
  private static final String ERROR_CONTENT =
      "{\"error\":{\"code\":401,\"errors\":[{\"domain\":\"global\","
      + "\"location\":\"Authorization\",\"locationType\":\"header\","
      + "\"message\":\"me\",\"reason\":\"authError\"}],\"message\":\"me\"}}";
  private String originalOsName;
  private String originalOsVersion;

  @Override
  protected void setUp() throws Exception {
    super.setUp();

    // save the original system properties so we can mock them out
    this.originalOsName = System.getProperty("os.name");
    this.originalOsVersion = System.getProperty("os.version");
  }

  @Override
  protected void tearDown() throws Exception {
    // restore the original system properties
    System.setProperty("os.name", originalOsName);
    System.setProperty("os.version", originalOsVersion);

    super.tearDown();
  }

  public void testExecuteUnparsed_error() throws Exception {
    HttpTransport transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(final String method, final String url) {
        return new MockLowLevelHttpRequest() {
          @Override
          public LowLevelHttpResponse execute() {
            assertEquals("GET", method);
            assertEquals("https://www.googleapis.com/test/path/v1/tests/foo", url);
            MockLowLevelHttpResponse result = new MockLowLevelHttpResponse();
            result.setStatusCode(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
            result.setContentType(Json.MEDIA_TYPE);
            result.setContent(ERROR_CONTENT);
            return result;
          }
        };
      }
    };
    MockGoogleClient client = new MockGoogleClient.Builder(
        transport, ROOT_URL, SERVICE_PATH, JSON_OBJECT_PARSER, null).setApplicationName(
        "Test Application").build();
    MockGoogleClientRequest<String> request = new MockGoogleClientRequest<String>(
        client, HttpMethods.GET, URI_TEMPLATE, null, String.class);
    try {
      request.put("testId", "foo");
      request.executeUnparsed();
      fail("expected " + HttpResponseException.class);
    } catch (HttpResponseException e) {
      // expected
      assertEquals("401" + StringUtils.LINE_SEPARATOR + ERROR_CONTENT, e.getMessage());
    }
  }

  public void testExecuteUsingHead() throws Exception {
    HttpTransport transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(final String method, final String url) {
        return new MockLowLevelHttpRequest() {
          @Override
          public LowLevelHttpResponse execute() {
            assertEquals("HEAD", method);
            assertEquals("https://www.googleapis.com/test/path/v1/tests/foo", url);
            return new MockLowLevelHttpResponse();
          }
        };
      }
    };
    MockGoogleClient client = new MockGoogleClient.Builder(
        transport, ROOT_URL, SERVICE_PATH, JSON_OBJECT_PARSER, null).setApplicationName(
        "Test Application").build();
    MockGoogleClientRequest<String> request = new MockGoogleClientRequest<String>(
        client, HttpMethods.GET, URI_TEMPLATE, null, String.class);
    request.put("testId", "foo");
    request.executeUsingHead();
  }

  public void testBuildHttpRequest_emptyContent() throws Exception {
    for (String method : Arrays.asList("GET", "HEAD", "DELETE", "FOO")) {
      subtestBuildHttpRequest_emptyContent(method, false);
    }
    for (String method : Arrays.asList("POST", "PUT", "PATCH")) {
      subtestBuildHttpRequest_emptyContent(method, true);
    }
  }

  private void subtestBuildHttpRequest_emptyContent(String method, boolean expectEmptyContent)
      throws Exception {
    HttpTransport transport = new MockHttpTransport();
    MockGoogleClient client = new MockGoogleClient.Builder(
        transport, ROOT_URL, SERVICE_PATH, JSON_OBJECT_PARSER, null).setApplicationName(
        "Test Application").build();
    MockGoogleClientRequest<String> request =
        new MockGoogleClientRequest<String>(client, method, URI_TEMPLATE, null, String.class);
    HttpRequest httpRequest = request.buildHttpRequest();
    if (expectEmptyContent) {
      assertTrue(httpRequest.getContent() instanceof EmptyContent);
    } else {
      assertNull(httpRequest.getContent());
    }
  }

  public void testCheckRequiredParameter() throws Exception {
    HttpTransport transport = new MockHttpTransport();
    MockGoogleClient client = new MockGoogleClient.Builder(
        transport, ROOT_URL, SERVICE_PATH, JSON_OBJECT_PARSER, null).setApplicationName(
        "Test Application").build();
    MockGoogleClientRequest<String> request = new MockGoogleClientRequest<String>(
        client, HttpMethods.GET, URI_TEMPLATE, null, String.class);

    // Should not throw an Exception.
    request.checkRequiredParameter("Not Null", "notNull()");

    try {
      request.checkRequiredParameter(null, "content.getTest().getAnotherTest()");
      fail("Expected " + IllegalArgumentException.class);
    } catch (IllegalArgumentException iae) {
      // Expected.
    }
  }

  public void testExecute_void() throws Exception {
    HttpTransport transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(final String method, final String url) {
        return new MockLowLevelHttpRequest() {
          @Override
          public LowLevelHttpResponse execute() {
            return new MockLowLevelHttpResponse().setContent("{\"a\":\"ignored\"}")
                .setContentType(Json.MEDIA_TYPE);
          }
        };
      }
    };
    MockGoogleClient client = new MockGoogleClient.Builder(
        transport, ROOT_URL, SERVICE_PATH, JSON_OBJECT_PARSER, null).setApplicationName(
        "Test Application").build();
    MockGoogleClientRequest<Void> request =
        new MockGoogleClientRequest<Void>(client, HttpMethods.GET, URI_TEMPLATE, null, Void.class);
    Void v = request.execute();
    assertNull(v);
  }

  public void testUserAgentSuffix() throws Exception {
    AssertUserAgentTransport transport = new AssertUserAgentTransport();
    // Specify an Application Name.
    String applicationName = "Test Application";
    transport.expectedUserAgent = applicationName + " "
        + "Google-API-Java-Client/" + GoogleUtils.VERSION + " "
        + HttpRequest.USER_AGENT_SUFFIX;
    MockGoogleClient client = new MockGoogleClient.Builder(
        transport, ROOT_URL, SERVICE_PATH, JSON_OBJECT_PARSER, null).setApplicationName(
            applicationName).build();
    MockGoogleClientRequest<Void> request =
        new MockGoogleClientRequest<Void>(client, HttpMethods.GET, URI_TEMPLATE, null, Void.class);
    request.executeUnparsed();
  }

  public void testUserAgent() throws IOException {
    AssertUserAgentTransport transport = new AssertUserAgentTransport();
    transport.expectedUserAgent = "Google-API-Java-Client/" + GoogleUtils.VERSION + " "
        + HttpRequest.USER_AGENT_SUFFIX;
    // Don't specify an Application Name.
    MockGoogleClient client = new MockGoogleClient.Builder(
        transport, ROOT_URL, SERVICE_PATH, JSON_OBJECT_PARSER, null).build();
    MockGoogleClientRequest<Void> request =
        new MockGoogleClientRequest<>(client, HttpMethods.GET, URI_TEMPLATE, null, Void.class);
    request.executeUnparsed();
  }

  public void testSetsApiClientHeader() throws IOException {
    HttpTransport transport = new AssertHeaderTransport("X-Goog-Api-Client", "gl-java/\\d+\\.\\d+\\.\\d+.*");
    MockGoogleClient client = new MockGoogleClient.Builder(
        transport, ROOT_URL, SERVICE_PATH, JSON_OBJECT_PARSER, null).build();
    MockGoogleClientRequest<Void> request =
        new MockGoogleClientRequest<>(client, HttpMethods.GET, URI_TEMPLATE, null, Void.class);
    request.executeUnparsed();
  }

  public void testSetsApiClientHeaderWithOsVersion() {
    System.setProperty("os.name", "My OS");
    System.setProperty("os.version", "1.2.3");

    String version = new ApiClientVersion().toString();
    assertTrue("Api version should contain the os version", version.matches(".* my-os/1.2.3"));
  }

  public void testSetsApiClientHeaderWithoutOsVersion() {
    System.setProperty("os.name", "My OS");
    System.clearProperty("os.version");
    assertNull(System.getProperty("os.version"));

    String version = new ApiClientVersion().toString();
    assertFalse("Api version should not contain the os version", version.matches(".*my-os.*"));
  }

  public void testSetsApiClientHeaderDiscoveryVersion() throws IOException {
    HttpTransport transport = new AssertHeaderTransport("X-Goog-Api-Client", ".*gdcl/\\d+\\.\\d+\\.\\d+.*");
    MockGoogleClient client = new MockGoogleClient.Builder(
            transport, ROOT_URL, SERVICE_PATH, JSON_OBJECT_PARSER, null).build();
    MockGoogleClientRequest<Void> request =
            new MockGoogleClientRequest<>(client, HttpMethods.GET, URI_TEMPLATE, null, Void.class);
    request.executeUnparsed();
  }

  public void testReturnRawInputStream_defaultFalse() throws Exception {
    HttpTransport transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(final String method, final String url) {
        return new MockLowLevelHttpRequest() {
          @Override
          public LowLevelHttpResponse execute() {
            return new MockLowLevelHttpResponse().setContentEncoding("gzip").setContent(new ByteArrayInputStream(
                BaseEncoding.base64()
                    .decode("H4sIAAAAAAAAAPNIzcnJV3DPz0/PSVVwzskvTVEILskvSkxPVQQA/LySchsAAAA=")));
          }
        };
      }
    };
    MockGoogleClient client = new MockGoogleClient.Builder(transport, ROOT_URL, SERVICE_PATH,
        JSON_OBJECT_PARSER, null).setApplicationName(
        "Test Application").build();
    MockGoogleClientRequest<String> request = new MockGoogleClientRequest<String>(
        client, HttpMethods.GET, URI_TEMPLATE, null, String.class);
    InputStream inputStream = request.executeAsInputStream();
    // The response will be wrapped because of gzip encoding
    assertFalse(inputStream instanceof ByteArrayInputStream);
  }

  public void testReturnRawInputStream_True() throws Exception {
    HttpTransport transport = new MockHttpTransport() {
      @Override
      public LowLevelHttpRequest buildRequest(final String method, final String url) {
        return new MockLowLevelHttpRequest() {
          @Override
          public LowLevelHttpResponse execute() {
            return new MockLowLevelHttpResponse().setContentEncoding("gzip").setContent(new ByteArrayInputStream(
                BaseEncoding.base64()
                    .decode("H4sIAAAAAAAAAPNIzcnJV3DPz0/PSVVwzskvTVEILskvSkxPVQQA/LySchsAAAA=")));
          }
        };
      }
    };
    MockGoogleClient client = new MockGoogleClient.Builder(transport, ROOT_URL, SERVICE_PATH,
        JSON_OBJECT_PARSER, null).setApplicationName(
        "Test Application").build();
    MockGoogleClientRequest<String> request = new MockGoogleClientRequest<String>(
        client, HttpMethods.GET, URI_TEMPLATE, null, String.class);
    request.setReturnRawInputStream(true);
    InputStream inputStream = request.executeAsInputStream();
    // The response will not be wrapped due to setReturnRawInputStream(true)
    assertTrue(inputStream instanceof ByteArrayInputStream);
  }

  private class AssertHeaderTransport extends MockHttpTransport {
    String expectedHeader;
    String expectedHeaderValue;

    AssertHeaderTransport(String header, String value) {
      expectedHeader = header;
      expectedHeaderValue = value;
    }

    @Override
    public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
      return new MockLowLevelHttpRequest() {
        @Override
        public LowLevelHttpResponse execute() throws IOException {
          String firstHeader = getFirstHeaderValue(expectedHeader);
          assertTrue(
              String.format(
                  "Expected header value to match %s, instead got %s.",
                  expectedHeaderValue,
                  firstHeader
              ),
              firstHeader.matches(expectedHeaderValue)
          );
          return new MockLowLevelHttpResponse();
        }
      };
    }
  }

  private class AssertUserAgentTransport extends MockHttpTransport {
    String expectedUserAgent;

    @Override
    public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
      return new MockLowLevelHttpRequest() {
        @Override
        public LowLevelHttpResponse execute() throws IOException {
          assertEquals(expectedUserAgent, getFirstHeaderValue("User-Agent"));
          return new MockLowLevelHttpResponse();
        }
      };
    }
  }
}