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

import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.jclouds.reflect.Reflection2.method;
import static org.testng.Assert.assertEquals;

import java.util.concurrent.TimeUnit;

import javax.inject.Named;

import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpCommandExecutorService;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.reflect.Invocation;
import org.jclouds.rest.config.InvocationConfig;
import org.jclouds.rest.internal.InvokeHttpMethod.InvokeAndTransform;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.TimeLimiter;

@Test(groups = "unit", singleThreaded = true)
public class InvokeHttpMethodTest {

   public interface ThingApi {
      @Named("ns:get")
      HttpResponse get();
   }

   private Invocation get;
   private HttpRequest getRequest = HttpRequest.builder().method("GET").endpoint("http://get").build();
   private HttpCommand getCommand = new HttpCommand(getRequest);
   private Function<Invocation, HttpRequest> toRequest;

   @BeforeClass
   void setupInvocations() throws SecurityException, NoSuchMethodException {
      get = Invocation.create(method(ThingApi.class, "get"), ImmutableList.of());
      toRequest = Functions.forMap(ImmutableMap.of(get, getRequest));
   }

   @SuppressWarnings("unchecked")
   private Function<HttpRequest, Function<HttpResponse, ?>> transformerForRequest = Function.class.cast(Functions
         .constant(Functions.identity()));

   private HttpResponse response = HttpResponse.builder().statusCode(200).payload("foo").build();
   private HttpCommandExecutorService http;
   private TimeLimiter timeLimiter;
   @SuppressWarnings("rawtypes")
   private org.jclouds.Fallback fallback;
   private InvocationConfig config;
   private InvokeHttpMethod invokeHttpMethod;


   @SuppressWarnings("unchecked")
   @BeforeMethod
   void createMocks() {
      http = createMock(HttpCommandExecutorService.class);
      timeLimiter = createMock(TimeLimiter.class);
      fallback = createMock(org.jclouds.Fallback.class);
      config = createMock(InvocationConfig.class);
      invokeHttpMethod = new InvokeHttpMethod(toRequest, http, transformerForRequest, timeLimiter, config);
      expect(config.getCommandName(get)).andReturn("ns:get");
      expect(config.getFallback(get)).andReturn(fallback);
   }

   @AfterMethod
   void verifyMocks() {
      verify(http, timeLimiter, fallback, config);
   }

   public void testMethodWithTimeoutRunsTimeLimiter() throws Exception {
      expect(config.getTimeoutNanos(get)).andReturn(Optional.of(250000000L));
      InvokeAndTransform invoke = invokeHttpMethod.new InvokeAndTransform("ns:get", getCommand);
      expect(timeLimiter.callWithTimeout(invoke, 250000000, TimeUnit.NANOSECONDS)).andReturn(response);
      replay(http, timeLimiter, fallback, config);
      invokeHttpMethod.apply(get);
   }

   public void testMethodWithNoTimeoutCallGetDirectly() throws Exception {
      expect(config.getTimeoutNanos(get)).andReturn(Optional.<Long> absent());
      expect(http.invoke(new HttpCommand(getRequest))).andReturn(response);
      replay(http, timeLimiter, fallback, config);
      invokeHttpMethod.apply(get);
   }

   private HttpResponse fallbackResponse = HttpResponse.builder().statusCode(200).payload("bar").build();

   public void testDirectCallRunsFallbackCreateOrPropagate() throws Exception {
      IllegalStateException exception = new IllegalStateException();
      expect(config.getTimeoutNanos(get)).andReturn(Optional.<Long> absent());
      expect(http.invoke(new HttpCommand(getRequest))).andThrow(exception);
      expect(fallback.createOrPropagate(exception)).andReturn(fallbackResponse);
      replay(http, timeLimiter, fallback, config);
      assertEquals(invokeHttpMethod.apply(get), fallbackResponse);
   }

   public void testTimeLimitedRunsFallbackCreateOrPropagate() throws Exception {
      IllegalStateException exception = new IllegalStateException();
      expect(config.getTimeoutNanos(get)).andReturn(Optional.of(250000000L));
      InvokeAndTransform invoke = invokeHttpMethod.new InvokeAndTransform("ns:get", getCommand);
      expect(timeLimiter.callWithTimeout(invoke, 250000000, TimeUnit.NANOSECONDS)).andThrow(exception);
      expect(fallback.createOrPropagate(exception)).andReturn(fallbackResponse);
      replay(http, timeLimiter, fallback, config);
      assertEquals(invokeHttpMethod.apply(get), fallbackResponse);
   }
}
