/*
 * Copyright (c) 2022 Airbyte, Inc., all rights reserved.
 */

package io.airbyte.server.services;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.io.IOException;
import java.net.http.HttpTimeoutException;
import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

public class AirbyteGithubStoreTest {

  private static final Duration TIMEOUT = Duration.ofSeconds(1);
  private static final String CONTENT_TYPE = "Content-Type";
  private static final String PLAIN_TEXT = "text/plain; charset=utf-8";
  private static final String CACHE_CONTROL = "Cache-Control";
  private static final String NO_CACHE = "no-cache";

  private MockWebServer webServer;
  private AirbyteGithubStore githubStore;

  @BeforeEach
  public void setUp() {
    webServer = new MockWebServer();
    githubStore = AirbyteGithubStore.test(webServer.url("/").toString(), TIMEOUT);
  }

  @Nested
  @DisplayName("when the additional definitions file is unusable, badly formatted, or cannot be retrieved due to errors")
  class FileUnusable {

    @Test
    void testGetLatestSourcesWithNonJson() throws InterruptedException {
      final var nonjsonBody = "irrelevant text";
      final var nonjsonResponse = new MockResponse().setResponseCode(200)
          .addHeader(CONTENT_TYPE, PLAIN_TEXT)
          .addHeader(CACHE_CONTROL, NO_CACHE)
          .setBody(nonjsonBody);
      webServer.enqueue(nonjsonResponse);
      assertEquals(Collections.emptyList(), githubStore.getLatestSources());
    }

    @Test
    void testGetLatestSourcesWithWrongSchemaJson() throws InterruptedException {
      final var jsonBody = "{ json: 'validButWrongFormat' }";
      final var jsonResponse = new MockResponse().setResponseCode(200)
          .addHeader(CONTENT_TYPE, "application/json; charset=utf-8")
          .addHeader(CACHE_CONTROL, NO_CACHE)
          .setBody(jsonBody);
      webServer.enqueue(jsonResponse);
      assertEquals(Collections.emptyList(), githubStore.getLatestSources());
    }

    @Test
    void testGetLatestDestinationsWithNonJson() throws InterruptedException {
      final var nonjsonBody = "irrelevant text";
      final var nonjsonResponse = new MockResponse().setResponseCode(200)
          .addHeader(CONTENT_TYPE, PLAIN_TEXT)
          .addHeader(CACHE_CONTROL, NO_CACHE)
          .setBody(nonjsonBody);
      webServer.enqueue(nonjsonResponse);
      assertEquals(Collections.emptyList(), githubStore.getLatestDestinations());
    }

    @Test
    void testGetLatestDestinationsWithWrongSchemaJson() throws InterruptedException {
      final var jsonBody = "{ json: 'validButWrongFormat' }";
      final var jsonResponse = new MockResponse().setResponseCode(200)
          .addHeader(CONTENT_TYPE, "application/json; charset=utf-8")
          .addHeader(CACHE_CONTROL, NO_CACHE)
          .setBody(jsonBody);
      webServer.enqueue(jsonResponse);
      assertEquals(Collections.emptyList(), githubStore.getLatestDestinations());
    }

  }

  @Nested
  @DisplayName("when there is no internet")
  class NoInternet {

    @Test
    void testGetLatestDestinations() throws InterruptedException, IOException {
      webServer.shutdown();
      assertEquals(Collections.emptyList(), githubStore.getLatestDestinations());
    }

    @Test
    void testGetLatestSources() throws InterruptedException, IOException {
      webServer.shutdown();
      assertEquals(Collections.emptyList(), githubStore.getLatestSources());
    }

  }

  @Nested
  @DisplayName("when a bad file is specified")
  class BadFile {

    @Test
    void testGetLatestDestinations() throws InterruptedException {
      final var timeoutResp = new MockResponse().setResponseCode(404);
      webServer.enqueue(timeoutResp);

      assertEquals(Collections.emptyList(), githubStore.getLatestDestinations());
    }

    @Test
    void testGetLatestSources() throws InterruptedException {
      final var timeoutResp = new MockResponse().setResponseCode(404);
      webServer.enqueue(timeoutResp);

      assertEquals(Collections.emptyList(), githubStore.getLatestSources());
    }

  }

  @Nested
  @DisplayName("getFile")
  class GetFile {

    @Test
    void testReturn() throws IOException, InterruptedException {
      final var goodBody = "great day!";
      final var goodResp = new MockResponse().setResponseCode(200)
          .addHeader(CONTENT_TYPE, PLAIN_TEXT)
          .addHeader(CACHE_CONTROL, NO_CACHE)
          .setBody(goodBody);
      webServer.enqueue(goodResp);

      final var fileStr = githubStore.getFile("test-file");
      assertEquals(goodBody, fileStr);
    }

    @Test
    void testHttpTimeout() {
      final var timeoutResp = new MockResponse().setResponseCode(200)
          .addHeader(CONTENT_TYPE, PLAIN_TEXT)
          .addHeader(CACHE_CONTROL, NO_CACHE)
          .setBody("")
          .setHeadersDelay(TIMEOUT.toSeconds() * 2, TimeUnit.SECONDS)
          .setBodyDelay(TIMEOUT.toSeconds() * 2, TimeUnit.SECONDS);
      webServer.enqueue(timeoutResp);

      assertThrows(HttpTimeoutException.class, () -> githubStore.getFile("test-file"));
    }

  }

}
