/*-
 * -\-\-
 * github-api
 * --
 * Copyright (C) 2016 - 2020 Spotify AB
 * --
 * Licensed 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 com.spotify.github.v3.clients;

import static com.google.common.io.Resources.getResource;
import static java.nio.charset.Charset.defaultCharset;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.core.Is.is;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Resources;
import com.spotify.github.FixtureHelper;
import com.spotify.github.v3.apps.InstallationRepositoriesResponse;
import com.spotify.github.v3.apps.requests.AccessTokenRequest;
import com.spotify.github.v3.apps.requests.ImmutableAccessTokenRequest;
import com.spotify.github.v3.checks.AccessToken;
import com.spotify.github.v3.checks.App;
import com.spotify.github.v3.checks.Installation;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class GithubAppClientTest {

  public final MockWebServer mockServer = new MockWebServer();

  private final ObjectMapper objectMapper = new ObjectMapper();
  private final int appId = 42;
  private GithubAppClient client;

  private static String getFixture(String resource) throws IOException {
    return Resources.toString(getResource(GithubAppClientTest.class, resource), defaultCharset());
  }

  @BeforeEach
  public void setUp() throws Exception {
    URI uri = mockServer.url("").uri();
    File key = FixtureHelper.loadFile("githubapp/key.pem");

    GitHubClient rootclient = GitHubClient.create(uri, key, appId);
    client = rootclient.createRepositoryClient("owner", "repo").createGithubAppClient();
  }

  @Test
  public void getInstallationsList() throws Exception {
    mockServer.enqueue(
        new MockResponse()
            .setResponseCode(200)
            .setBody(FixtureHelper.loadFixture("githubapp/installations-list.json")));

    List<Installation> installations = client.getInstallations().join();

    assertThat(installations.size(), is(2));
    assertThat(installations.get(0).account().login(), is("github"));
    assertThat(installations.get(0).id(), is(1));
    assertThat(installations.get(1).account().login(), is("octocat"));
    assertThat(installations.get(1).id(), is(3));

    RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS);
    assertThat(recordedRequest.getRequestUrl().encodedPath(), is("/app/installations"));
    assertThat(recordedRequest.getRequestUrl().queryParameter("per_page"), is("100"));

    assertThat(
        recordedRequest.getHeaders().values("Accept"),
        containsInAnyOrder("application/json", "application/vnd.github.machine-man-preview+json"));
  }

  @Test
  public void listAccessibleRepositories() throws Exception {
    // response for POST /app/installations/:id/access_tokens
    final String installationAccessToken = "abc123-secret";
    mockServer.enqueue(
        new MockResponse()
            .setResponseCode(201)
            // this might not serialize 100% the same as the Json class's ObjectMapper but should be
            // fine for this test
            .setBody(
                objectMapper
                    .createObjectNode()
                    .put("token", installationAccessToken)
                    .put("expires_at", ZonedDateTime.now().plusHours(1).toString())
                    .toString()));

    // response for GET /installation/repositories
    mockServer.enqueue(
        new MockResponse()
            .setResponseCode(200)
            .setBody(FixtureHelper.loadFixture("githubapp/accessible-repositories.json")));

    InstallationRepositoriesResponse response = client.listAccessibleRepositories(1234).join();

    assertThat(response.totalCount(), is(2));
    assertThat(response.repositories().size(), is(2));
    assertThat(response.repositories().get(0).id(), is(1));
    assertThat(response.repositories().get(1).id(), is(2));

    RecordedRequest accessTokenRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS);
    assertThat(accessTokenRequest.getMethod(), is("POST"));
    assertThat(
        accessTokenRequest.getRequestUrl().encodedPath(),
        is("/app/installations/1234/access_tokens"));

    RecordedRequest listReposRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS);
    assertThat(listReposRequest.getMethod(), is("GET"));
    assertThat(listReposRequest.getRequestUrl().encodedPath(), is("/installation/repositories"));
  }

  @Test
  public void getInstallation() throws Exception {
    mockServer.enqueue(
        new MockResponse()
            .setResponseCode(200)
            .setBody(FixtureHelper.loadFixture("githubapp/installation.json")));

    Installation installation = client.getInstallation().join();

    assertThat(installation.id(), is(1));
    assertThat(installation.account().login(), is("github"));

    RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS);
    assertThat(recordedRequest.getRequestUrl().encodedPath(), is("/repos/owner/repo/installation"));
  }

  @Test
  public void getInstallationByInstallationId() throws Exception {
    mockServer.enqueue(
        new MockResponse()
            .setResponseCode(200)
            .setBody(FixtureHelper.loadFixture("githubapp/installation.json")));

    Installation installation = client.getInstallation(1234).join();

    assertThat(installation.id(), is(1));
    assertThat(installation.account().login(), is("github"));

    RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS);
    assertThat(recordedRequest.getRequestUrl().encodedPath(), is("/app/installations/1234"));
  }

  @Test
  public void getAuthenticatedApp() throws Exception {
    mockServer.enqueue(
        new MockResponse()
            .setResponseCode(200)
            .setBody(FixtureHelper.loadFixture("githubapp/authenticated-app.json")));

    App app = client.getAuthenticatedApp().join();

    assertThat(app.id(), is(1));
    assertThat(app.slug().get(), is("octoapp"));
    assertThat(app.name(), is("Octocat App"));
    assertThat(app.clientId().get(), is("Iv1.8a61f9b3a7aba766"));
    assertThat(app.installationsCount().get(), is(5));

    RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS);
    assertThat(recordedRequest.getRequestUrl().encodedPath(), is("/app"));
    assertThat(
        recordedRequest.getHeaders().values("Authorization").get(0).startsWith("Bearer "),
        is(true));
  }

  @Test
  public void getAccessTokenWithoutScoping() throws Exception {
    mockServer.enqueue(
        new MockResponse()
            .setResponseCode(201)
            .setBody(FixtureHelper.loadFixture("githubapp/access-token.json")));

    AccessToken token = client.getAccessToken(1234).join();

    assertThat(token.token(), is("ghs_16C7e42F292c6912E7710c838347Ae178B4a"));

    RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS);
    assertThat(recordedRequest.getMethod(), is("POST"));
    assertThat(
        recordedRequest.getRequestUrl().encodedPath(),
        is("/app/installations/1234/access_tokens"));
    assertThat(recordedRequest.getBody().readUtf8(), is(""));
  }

  @Test
  public void getAccessTokenWithRepositoryScoping() throws Exception {
    mockServer.enqueue(
        new MockResponse()
            .setResponseCode(201)
            .setBody(FixtureHelper.loadFixture("githubapp/access-token.json")));

    AccessTokenRequest request = ImmutableAccessTokenRequest.builder()
        .repositories(List.of("Hello-World"))
        .repositoryIds(List.of(1))
        .build();

    AccessToken token = client.getAccessToken(1234, request).join();

    assertThat(token.token(), is("ghs_16C7e42F292c6912E7710c838347Ae178B4a"));

    RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS);
    assertThat(recordedRequest.getMethod(), is("POST"));
    assertThat(
        recordedRequest.getRequestUrl().encodedPath(),
        is("/app/installations/1234/access_tokens"));

    String requestBody = recordedRequest.getBody().readUtf8();
    assertThat(requestBody, containsString("\"repositories\":[\"Hello-World\"]"));
    assertThat(requestBody, containsString("\"repository_ids\":[1]"));
  }

  @Test
  public void getAccessTokenWithEmptyRequest() throws Exception {
    mockServer.enqueue(
        new MockResponse()
            .setResponseCode(201)
            .setBody(FixtureHelper.loadFixture("githubapp/access-token.json")));

    AccessTokenRequest emptyRequest = ImmutableAccessTokenRequest.builder().build();

    AccessToken token = client.getAccessToken(1234, emptyRequest).join();

    assertThat(token.token(), is("ghs_16C7e42F292c6912E7710c838347Ae178B4a"));

    RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS);
    String requestBody = recordedRequest.getBody().readUtf8();
    assertThat(requestBody, is("{}"));
  }
}
