package feign.vertx;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
import static feign.vertx.TestUtils.MAPPER;
import static org.assertj.core.api.Assertions.assertThat;

import com.github.tomakehurst.wiremock.junit.WireMockRule;
import feign.Logger;
import feign.RetryableException;
import feign.Retryer;
import feign.VertxFeign;
import feign.jackson.JacksonDecoder;
import feign.slf4j.Slf4jLogger;
import feign.vertx.testcase.IcecreamServiceApi;
import feign.vertx.testcase.domain.Flavor;
import io.vertx.core.Vertx;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Arrays;
import java.util.stream.Collectors;

@RunWith(VertxUnitRunner.class)
public class RetryingTest {
  private Vertx vertx = Vertx.vertx();

  @Rule
  public WireMockRule wireMockRule = new WireMockRule(8089);

  @Test
  public void testRetrying_success(TestContext context) {

    /* Given */
    String flavorsStr = Arrays
        .stream(Flavor.values())
        .map(flavor -> "\"" + flavor + "\"")
        .collect(Collectors.joining(", ", "[ ", " ]"));

    String scenario = "testRetrying_success";

    stubFor(get(urlEqualTo("/icecream/flavors"))
        .withHeader("Accept", equalTo("application/json"))
        .inScenario(scenario)
        .whenScenarioStateIs(STARTED)
        .willReturn(aResponse()
            .withStatus(503)
            .withHeader("Retry-After", "1"))
        .willSetStateTo("attempt1"));

    stubFor(get(urlEqualTo("/icecream/flavors"))
        .withHeader("Accept", equalTo("application/json"))
        .inScenario(scenario)
        .whenScenarioStateIs("attempt1")
        .willReturn(aResponse()
            .withStatus(503)
            .withHeader("Retry-After", "1"))
        .willSetStateTo("attempt2"));

    stubFor(get(urlEqualTo("/icecream/flavors"))
        .withHeader("Accept", equalTo("application/json"))
        .inScenario(scenario)
        .whenScenarioStateIs("attempt2")
        .willReturn(aResponse()
            .withStatus(200)
            .withHeader("Content-Type", "application/json")
            .withBody(flavorsStr)));

    IcecreamServiceApi client = VertxFeign
        .builder()
        .vertx(vertx)
        .decoder(new JacksonDecoder(MAPPER))
        .retryer(new Retryer.Default())
        .target(IcecreamServiceApi.class, "http://localhost:8089");

    Async async = context.async();

    /* When */
    client.getAvailableFlavors().setHandler(res -> {

      /* Then */
      if (res.succeeded()) {
        try {
          assertThat(res.result())
              .hasSize(Flavor.values().length)
              .containsAll(Arrays.asList(Flavor.values()));
          async.complete();
        } catch (Throwable exception) {
          context.fail(exception);
        }
      } else {
        context.fail(res.cause());
      }
    });
  }

  @Test
  public void testRetrying_noMoreAttempts(TestContext context) {

    /* Given */
    stubFor(get(urlEqualTo("/icecream/flavors"))
        .withHeader("Accept", equalTo("application/json"))
        .willReturn(aResponse()
            .withStatus(503)
            .withHeader("Retry-After", "1")));

    IcecreamServiceApi client = VertxFeign
        .builder()
        .vertx(vertx)
        .decoder(new JacksonDecoder(MAPPER))
        .retryer(new Retryer.Default())
        .logger(new Slf4jLogger())
        .logLevel(Logger.Level.FULL)
        .target(IcecreamServiceApi.class, "http://localhost:8089");

    Async async = context.async();

    /* When */
    client.getAvailableFlavors().setHandler(res -> {

      /* Then */
      if (res.failed())

      /* Then */
        if (res.failed()) {
          try {
            assertThat(res.cause())
                .isInstanceOf(RetryableException.class)
                .hasMessageContaining("status 503");
            async.complete();
          } catch (Throwable exception) {
            context.fail(exception);
          }
        } else {
          context.fail("RetryableException excepted but not occurred");
        }
    });
  }
}