/*
 * 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.aws.ec2.internal;

import static com.google.common.base.Throwables.propagate;
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService;
import static javax.ws.rs.core.MediaType.APPLICATION_XML;
import static org.assertj.core.api.Assertions.assertThat;
import static org.jclouds.util.Strings2.toStringAndClose;
import static org.testng.Assert.assertEquals;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.ws.rs.core.Response;

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

import org.jclouds.Constants;
import org.jclouds.ContextBuilder;
import org.jclouds.aws.ec2.AWSEC2Api;
import org.jclouds.aws.ec2.AWSEC2ProviderMetadata;
import org.jclouds.aws.ec2.config.AWSEC2HttpApiModule;
import org.jclouds.aws.filters.FormSignerV4.ServiceAndRegion;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.config.ComputeServiceProperties;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.date.DateService;
import org.jclouds.rest.ConfiguresHttpApi;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.inject.Module;
import com.google.inject.Provides;

/**
 * Tests need to run {@code singleThreaded = true} as otherwise tests will clash on the regionToServers field.
 * Sharing the regionToServers field means less code to write.
 */
public class BaseAWSEC2ApiMockTest {
   protected static final String DEFAULT_REGION = "us-east-1";

   // Example keys from http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
   private static final String ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE";
   private static final String SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";

   private Map<String, MockWebServer> regionToServers = Maps.newLinkedHashMap();

   protected AWSEC2Api api() {
      return builder(new Properties()).buildApi(AWSEC2Api.class);
   }

   protected ComputeService computeService() {
      return builder(new Properties()).buildView(ComputeServiceContext.class).getComputeService();
   }

   protected ContextBuilder builder(Properties overrides) {
      MockWebServer defaultServer = regionToServers.get(DEFAULT_REGION);
      overrides.setProperty(Constants.PROPERTY_MAX_RETRIES, "1");
      overrides.setProperty(ComputeServiceProperties.TIMEOUT_CLEANUP_INCIDENTAL_RESOURCES, "0");
      return ContextBuilder.newBuilder(new AWSEC2ProviderMetadata())
            .credentials(ACCESS_KEY, SECRET_KEY)
            .endpoint(defaultServer.url("").toString())
            .overrides(overrides)
            .modules(modules);
   }

   private final Set<Module> modules = ImmutableSet
         .<Module>of(new MockAWSEC2HttpApiModule(), new ExecutorServiceModule(newDirectExecutorService()));

   @ConfiguresHttpApi
   class MockAWSEC2HttpApiModule extends AWSEC2HttpApiModule {

      @Override
      protected String provideTimeStamp(DateService dateService) {
         return "20120416T155408Z";
      }

      @Provides ServiceAndRegion serviceAndRegion(){
         return new ServiceAndRegion() {
            @Override public String service() {
               return "ec2";
            }

            @Override public String region(String host) {
               for (Map.Entry<String, MockWebServer> regionToServer : regionToServers.entrySet()) {
                  MockWebServer server = regionToServer.getValue();
                  if (host.equals(server.getHostName() + ":" + regionToServer.getValue().getPort())) {
                     return regionToServer.getKey();
                  }
               }
               throw new IllegalStateException(host + " not found");
            }
         };
      }
   }

   @BeforeMethod(alwaysRun = true)
   public void start() throws IOException {
      MockWebServer server = new MockWebServer();
      server.start();
      regionToServers.put(DEFAULT_REGION, server);
   }

   @AfterMethod(alwaysRun = true)
   public void stop() throws IOException {
      for (MockWebServer server : regionToServers.values()) {
         server.shutdown();
      }
   }

   protected void enqueue(String region, MockResponse response) {
      regionToServers.get(region).enqueue(response);
   }

   protected void enqueueRegions(String... regions) throws IOException {
      StringBuilder describeRegionsResponse = new StringBuilder();
      describeRegionsResponse.append("<DescribeRegionsResponse>");
      for (String region : regions) {
         describeRegionsResponse.append("<item>");
         describeRegionsResponse.append("<regionName>").append(region).append("</regionName>");
         if (!regionToServers.containsKey(region)) {
            MockWebServer server = new MockWebServer();
            server.start();
            regionToServers.put(region, server);
         }
         MockWebServer server = regionToServers.get(region);
         String regionEndpoint = server.url("").toString();
         describeRegionsResponse.append("<regionEndpoint>").append(regionEndpoint).append("</regionEndpoint>");
         describeRegionsResponse.append("</item>");
      }
      describeRegionsResponse.append("</DescribeRegionsResponse>");
      enqueue(DEFAULT_REGION,
            new MockResponse().addHeader(CONTENT_TYPE, APPLICATION_XML).setBody(describeRegionsResponse.toString()));
   }

   protected void enqueueXml(Response.Status status, String region, String resource) {
      enqueue(region, new MockResponse()
         .setStatus("HTTP/1.1 " + status.getStatusCode() + " " + status.getReasonPhrase())
         .addHeader(CONTENT_TYPE, APPLICATION_XML)
         .setBody(stringFromResource(resource)));
   }

   protected void enqueueXml(String region, String resource) {
      enqueue(region,
            new MockResponse().addHeader(CONTENT_TYPE, APPLICATION_XML).setBody(stringFromResource(resource)));
   }

   protected String stringFromResource(String resourceName) {
      try {
         final InputStream resourceAsStream = getClass().getResourceAsStream(resourceName);
         if (resourceAsStream == null) {
            throw new IllegalArgumentException(
               "Could not find resource '" + resourceName + "' in class " + getClass().getSimpleName());
         }
         return toStringAndClose(resourceAsStream);
      } catch (IOException e) {
         throw propagate(e);
      }
   }


   protected RecordedRequest assertPosted(String region, String postParams) throws InterruptedException {
      return assertPosted(region, postParams, "2012-06-01");
   }

   protected RecordedRequest assertPosted(String region, String postParams, String apiVersion) throws InterruptedException {
      RecordedRequest request = regionToServers.get(region).takeRequest();
      assertEquals(request.getMethod(), "POST");
      assertEquals(request.getPath(), "/");
      assertEquals(request.getHeader("X-Amz-Date"), "20120416T155408Z");
      assertThat(
            request.getHeader(AUTHORIZATION)).startsWith("AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20120416/" +
            region + "/ec2/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=");
      String body = request.getBody().readUtf8();
      assertThat(body).contains("&Version=" + apiVersion);
      assertEquals(body.replace("&Version=" + apiVersion, ""), postParams);
      return request;
   }
}
