/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.jclouds.azurecompute.arm.internal;

import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService;
import static org.assertj.core.util.Sets.newHashSet;
import static org.jclouds.oauth.v2.config.CredentialType.BEARER_TOKEN_CREDENTIALS;
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
import static org.testng.Assert.assertEquals;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;
import java.util.Set;

import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;

import org.jclouds.ContextBuilder;
import org.jclouds.azurecompute.arm.AzureComputeApi;
import org.jclouds.azurecompute.arm.AzureComputeProviderMetadata;
import org.jclouds.azurecompute.arm.AzureManagementApiMetadata;
import org.jclouds.azurecompute.arm.config.AzureComputeHttpApiModule;
import org.jclouds.azurecompute.arm.config.GraphRBAC;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.date.DateService;
import org.jclouds.providers.ProviderMetadata;
import org.jclouds.rest.ApiContext;
import org.jclouds.rest.ConfiguresHttpApi;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;

import com.google.common.base.Charsets;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources;
import com.google.gson.JsonParser;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;


public class BaseAzureComputeApiMockTest {

   private static final String MOCK_BEARER_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9";
   private static final String DEFAULT_ENDPOINT = new AzureComputeProviderMetadata().getEndpoint();

   protected MockWebServer server;
   protected AzureComputeApi api;
   protected ApiContext<AzureComputeApi> context;
   protected DateService dateService;

   // So that we can ignore formatting.
   private final JsonParser parser = new JsonParser();

   @BeforeMethod
   public void start() throws IOException, URISyntaxException {
      server = new MockWebServer();
      server.start();
      
      context = ContextBuilder.newBuilder(testProviderMetadata())
              .credentials("mock", MOCK_BEARER_TOKEN)
              .endpoint(server.url("/").toString() + "subscriptions/SUBSCRIPTIONID")
              .modules(setupModules())
              .overrides(setupProperties())
              .build();
      api = context.getApi();
      dateService = context.utils().injector().getInstance(DateService.class);
   }
   
   protected ProviderMetadata testProviderMetadata() {
      // Omit the default HTTP API modules to allow overriding
      Set<Class<? extends Module>> defaultModules = newHashSet(filter(
            new AzureManagementApiMetadata().getDefaultModules(),
            not(Predicates.<Class<? extends Module>> equalTo(AzureComputeHttpApiModule.class))));
      return AzureComputeProviderMetadata.builder()
            .apiMetadata(AzureManagementApiMetadata.builder().defaultModules(defaultModules).build()).build();
   }

   protected Properties setupProperties() {
      Properties properties = new Properties();
      properties.put(CREDENTIAL_TYPE, BEARER_TOKEN_CREDENTIALS.toString());
      properties.put("oauth.endpoint", "https://login.microsoftonline.com/tenant-id/oauth2/token");
      return properties;
   }

   protected Set<Module> setupModules() {
      ImmutableSet.Builder<Module> modules = ImmutableSet.builder();
      modules.add(new ExecutorServiceModule(newDirectExecutorService()));
      // Override the default HTTP module to accomodate custom bindings for the
      // hardcoded endpoints such as the Graph RBAC API one.
      modules.add(new TestAzureComputeHttpApiModule(server));
      return modules.build();
   }
   
   @AfterMethod(alwaysRun = true)
   public void stop() throws IOException {
      server.shutdown();
      api.close();
   }

   protected String url(String path) {
      return server.url(path).toString();
   }

   protected MockResponse jsonResponse(String resource) {
      return new MockResponse().addHeader("Content-Type", "application/json").setBody(stringFromResource(resource));
   }

   protected MockResponse response404() {
      return new MockResponse().setStatus("HTTP/1.1 404 Not Found");
   }

   protected MockResponse response200() {
      return new MockResponse().setStatus("HTTP/1.1 200 OK");
   }

   protected MockResponse response202() {
      return new MockResponse().setStatus("HTTP/1.1 202 Accepted");
   }

   protected MockResponse response204() {
      return new MockResponse().setStatus("HTTP/1.1 204 No Content");
   }

   protected MockResponse response202WithHeader() {
      return new MockResponse()
            .setStatus("HTTP/1.1 202 Accepted")
            .addHeader(
                  "Location",
                  "https://management.azure.com/subscriptions/SUBSCRIPTIONID/operationresults/eyJqb2JJZCI6IlJFU09VUkNFR1JPVVBERUxFVElPTkpPQi1SVEVTVC1DRU5UUkFMVVMiLCJqb2JMb2NhdGlvbiI6ImNlbnRyYWx1cyJ9?api-version=2014-04-01");
   }

   protected MockResponse response200WithHeader() {
      return new MockResponse()
              .setStatus("HTTP/1.1 200 O")
              .addHeader("Location", "https://management.azure.com/subscriptions/SUBSCRIPTIONID/operationresults/eyJqb2JJZCI6IlJFU09VUkNFR1JPVVBERUxFVElPTkpPQi1SVEVTVC1DRU5UUkFMVVMiLCJqb2JMb2NhdGlvbiI6ImNlbnRyYWx1cyJ9?api-version=2014-04-01");
   }

   protected String stringFromResource(String resourceName) {
      try {
         String rsrc = Resources.toString(getClass().getResource(resourceName), Charsets.UTF_8);
         if (rsrc.contains(DEFAULT_ENDPOINT)) {
            rsrc = rsrc.replace(DEFAULT_ENDPOINT, url(""));
         }
         return rsrc;
      } catch (IOException e) {
         throw Throwables.propagate(e);
      }
   }

   protected RecordedRequest assertSent(MockWebServer server, String method, String path) throws InterruptedException {
      RecordedRequest request = server.takeRequest();
      assertEquals(request.getMethod(), method);
      assertEquals(request.getPath(), path);
      assertEquals(request.getHeader("Accept"), "application/json");
      assertEquals(request.getHeader("Authorization"), "Bearer " + MOCK_BEARER_TOKEN);
      return request;
   }

   protected RecordedRequest assertSent(MockWebServer server, String method, String path, String json)
         throws InterruptedException {
      RecordedRequest request = assertSent(server, method, path);
      assertEquals(request.getHeader("Content-Type"), "application/json");
      assertEquals(parser.parse(request.getBody().readUtf8()), parser.parse(json));
      return request;
   }
   
   @ConfiguresHttpApi
   private static class TestAzureComputeHttpApiModule extends AzureComputeHttpApiModule {
      private final MockWebServer server;
      
      public TestAzureComputeHttpApiModule(MockWebServer server) {
         this.server = server;
      }

      @Override
      protected void bindServiceEndpoints() {
         // Override the hardcoded service URIs to allow mocking service endpoints
         bind(new TypeLiteral<Supplier<URI>>() {
         }).annotatedWith(GraphRBAC.class).toInstance(
               Suppliers.ofInstance(URI.create(server.url("/graphrbac").toString() + "/tenant-id")));
      }
   }
}
