package com.apollographql.apollo;

import com.apollographql.apollo.api.Response;
import com.apollographql.apollo.api.cache.http.HttpCache;
import com.apollographql.apollo.api.cache.http.HttpCachePolicy;
import com.apollographql.apollo.cache.http.ApolloHttpCache;
import com.apollographql.apollo.cache.http.DiskLruHttpCacheStore;
import com.apollographql.apollo.cache.http.internal.FileSystem;
import com.apollographql.apollo.exception.ApolloException;
import com.apollographql.apollo.integration.httpcache.AllPlanetsQuery;
import com.apollographql.apollo.rx2.Rx2Apollo;
import io.reactivex.functions.Predicate;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Dispatcher;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import static com.google.common.truth.Truth.assertThat;

public class ApolloPrefetchTest {
  private ApolloClient apolloClient;
  @Rule public final MockWebServer server = new MockWebServer();
  @Rule public final InMemoryFileSystem inMemoryFileSystem = new InMemoryFileSystem();
  okhttp3.Request lastHttRequest;
  okhttp3.Response lastHttResponse;
  private MockHttpCacheStore cacheStore;
  private OkHttpClient okHttpClient;

  @Before
  public void setup() {
    cacheStore = new MockHttpCacheStore();
    cacheStore.delegate = new DiskLruHttpCacheStore(inMemoryFileSystem, new File("/cache/"), Integer.MAX_VALUE);
    okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(new Interceptor() {
          @Override public okhttp3.Response intercept(Chain chain) throws IOException {
            lastHttRequest = chain.request();
            lastHttResponse = chain.proceed(lastHttRequest);
            return lastHttResponse;
          }
        })
        .dispatcher(new Dispatcher(Utils.INSTANCE.immediateExecutorService()))
        .build();

    apolloClient = ApolloClient.builder()
        .serverUrl(server.url("/"))
        .okHttpClient(okHttpClient)
        .dispatcher(Utils.INSTANCE.immediateExecutor())
        .httpCache(new ApolloHttpCache(cacheStore, null))
        .build();
  }

  @After public void tearDown() {
    apolloClient.clearHttpCache();
  }

  @Test public void prefetchDefault() throws IOException, ApolloException {
    server.enqueue(Utils.INSTANCE.mockResponse("HttpCacheTestAllPlanets.json"));
    prefetch(apolloClient.prefetch(new AllPlanetsQuery()));
    checkCachedResponse("HttpCacheTestAllPlanets.json");
    Utils.INSTANCE.assertResponse(
        apolloClient
            .query(new AllPlanetsQuery())
            .httpCachePolicy(HttpCachePolicy.CACHE_ONLY.expireAfter(2, TimeUnit.SECONDS)),
        new Predicate<Response<AllPlanetsQuery.Data>>() {
          @Override public boolean test(Response<AllPlanetsQuery.Data> dataResponse) throws Exception {
            return !dataResponse.hasErrors();
          }
        }
    );
  }

  @Test public void prefetchNoCacheStore() throws Exception {
    ApolloClient apolloClient = ApolloClient.builder()
        .serverUrl(server.url("/"))
        .okHttpClient(okHttpClient)
        .dispatcher(Utils.INSTANCE.immediateExecutor())
        .build();

    server.enqueue(Utils.INSTANCE.mockResponse("HttpCacheTestAllPlanets.json"));
    prefetch(apolloClient.prefetch(new AllPlanetsQuery()));

    Utils.INSTANCE.enqueueAndAssertResponse(
        server,
        "HttpCacheTestAllPlanets.json",
        apolloClient.query(new AllPlanetsQuery()),
        new Predicate<Response<AllPlanetsQuery.Data>>() {
          @Override public boolean test(Response<AllPlanetsQuery.Data> response) throws Exception {
            return !response.hasErrors();
          }
        }
    );
  }

  @Test public void prefetchFileSystemWriteFailure() throws IOException {
    FaultyHttpCacheStore faultyCacheStore = new FaultyHttpCacheStore(FileSystem.SYSTEM);
    cacheStore.delegate = faultyCacheStore;

    faultyCacheStore.failStrategy(FaultyHttpCacheStore.FailStrategy.FAIL_HEADER_WRITE);
    server.enqueue(Utils.INSTANCE.mockResponse("HttpCacheTestAllPlanets.json"));

    Rx2Apollo.from(apolloClient.prefetch(new AllPlanetsQuery()))
        .test()
        .assertError(Exception.class);
    checkNoCachedResponse();

    server.enqueue(Utils.INSTANCE.mockResponse("HttpCacheTestAllPlanets.json"));
    faultyCacheStore.failStrategy(FaultyHttpCacheStore.FailStrategy.FAIL_BODY_WRITE);
    Rx2Apollo.from(apolloClient.prefetch(new AllPlanetsQuery()))
        .test()
        .assertError(Exception.class);
    checkNoCachedResponse();
  }

  private void checkCachedResponse(String fileName) throws IOException {
    String cacheKey = lastHttRequest.headers(HttpCache.CACHE_KEY_HEADER).get(0);
    okhttp3.Response response = apolloClient.cachedHttpResponse(cacheKey);
    assertThat(response).isNotNull();
    assertThat(response.body().source().readUtf8()).isEqualTo(Utils.INSTANCE.readFileToString(getClass(), "/" + fileName));
    response.body().source().close();
  }

  private void checkNoCachedResponse() throws IOException {
    String cacheKey = lastHttRequest.header(HttpCache.CACHE_KEY_HEADER);
    okhttp3.Response cachedResponse = apolloClient.cachedHttpResponse(cacheKey);
    assertThat(cachedResponse).isNull();
  }

  @SuppressWarnings("CheckReturnValue")
  private static void prefetch(ApolloPrefetch prefetch) {
    Rx2Apollo.from(prefetch).test();
  }
}