package it.com.atlassian.bitbucket.contract.consumer.importer;

import au.com.dius.pact.consumer.Pact;
import au.com.dius.pact.consumer.PactProviderRule;
import au.com.dius.pact.consumer.PactVerification;
import au.com.dius.pact.consumer.dsl.DslPart;
import au.com.dius.pact.consumer.dsl.PactDslJsonArray;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.model.PactConfig;
import au.com.dius.pact.model.PactFragment;
import au.com.dius.pact.model.PactSpecVersion;
import com.atlassian.bitbucket.internal.importer.OAuthScope;
import com.atlassian.bitbucket.internal.importer.SourceType;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.HttpHeaders;
import com.jayway.restassured.RestAssured;
import org.apache.commons.codec.binary.Base64;
import org.junit.Rule;
import org.junit.Test;

import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import static com.atlassian.bitbucket.test.DefaultFuncTestData.getAdminPassword;
import static com.atlassian.bitbucket.test.DefaultFuncTestData.getAdminUser;
import static it.com.atlassian.bitbucket.contract.consumer.importer.ImporterConsumerHelper.*;
import static javax.ws.rs.core.Response.Status.OK;
import static org.hamcrest.Matchers.equalTo;

public class GithubConsumerTest {

    private static final String MOCK_GITHUB_API_HOST = System.getProperty("pact.provider.host", "localhost");
    private static final int MOCK_GITHUB_API_PORT = Integer.parseInt(System.getProperty("pact.provider.port", "9234"));
    
    private static final String GITHUB_USER = System.getProperty("github.user", "<GitHub username>");
    private static final String GITHUB_TOKEN = System.getProperty("github.token", "<GitHub token>");

    private static final String GITHUB_ORG = "stash-test-org";
    private static final String GITHUB_ORG_AVATAR_URL = "https://avatars.githubusercontent.com/u/168166?v=3";

    private static final String REPO_CLONE_URL = "https://github.com/stash-test-org/nifi.git";
    private static final String REPO_NAME = "nifi";
    private static final String REPO_DESCRIPTION = "repository description";
    private static final String GITHUB_USER_AVATAR_URL = "https://avatars.githubusercontent.com/u/20569392?v=3";

    private static final String ENCODED_AUTH_STRING = Base64.encodeBase64String((GITHUB_USER + ':' + GITHUB_TOKEN)
            .getBytes(StandardCharsets.UTF_8));

    @Rule
    public PactProviderRule mockProvider = new PactProviderRule("github", MOCK_GITHUB_API_HOST, MOCK_GITHUB_API_PORT, false,
            PactConfig.apply(PactSpecVersion.V3), this);

    @Pact(provider = "github", consumer = "bitbucket_importer_plugin")
    public PactFragment createGetUserFragment(PactDslWithProvider builder) throws IOException {
        DslPart user = new PactDslJsonBody()
                .stringType("login", GITHUB_USER)
                .stringType("avatar_url", GITHUB_USER_AVATAR_URL);

        return builder
                .given("testing")
                .uponReceiving("Get user")
                .path("/users/" + GITHUB_USER)
                .headers(ImmutableMap.of(
                        HttpHeaders.AUTHORIZATION, "Basic " + ENCODED_AUTH_STRING))
                .method("GET")
                .willRespondWith()
                .status(200)
                .body(user)
                .headers(ImmutableMap.of("Content-Type", "application/json; charset=utf-8"))
                .toFragment();
    }

    @Pact(provider = "github", consumer = "bitbucket_importer_plugin")
    public PactFragment createListOrganizationFragment(PactDslWithProvider builder) throws IOException {
        DslPart owners = PactDslJsonArray.arrayEachLike()
                .stringType("login", GITHUB_ORG)
                .stringType("avatar_url", GITHUB_ORG_AVATAR_URL)
                .stringType("description", "stash test org")
                .closeObject();

        return builder
                .given("testing")
                .uponReceiving("List organizations")
                .path("/user/orgs")
                .headers(ImmutableMap.of(
                        HttpHeaders.AUTHORIZATION, "Basic " + ENCODED_AUTH_STRING))
                .query("page=1&per_page=5")
                .method("GET")
                .willRespondWith()
                .status(200)
                .body(owners)
                .headers(ImmutableMap.of("Content-Type", "application/json; charset=utf-8"))
                .toFragment();
    }

    @Pact(provider = "github", consumer = "bitbucket_importer_plugin")
    public PactFragment createListRepositoriesFragment(PactDslWithProvider builder) throws IOException {
        DslPart repositories = new PactDslJsonBody()
                .minArrayLike("items", 5)
                .stringType("clone_url", REPO_CLONE_URL)
                .stringType("description", REPO_DESCRIPTION)
                .stringType("name", REPO_NAME)
                .closeObject();

        return builder
                .given("testing")
                .uponReceiving("List Repositories")
                .path("/search/repositories")
                .headers(ImmutableMap.of(
                        HttpHeaders.AUTHORIZATION, "Basic " + ENCODED_AUTH_STRING))
                .query("q=user:stash-service-user fork:true &page=1&per_page=5")
                .method("GET")
                .willRespondWith()
                .status(200)
                .body(repositories)
                .matchHeader("Content-Type", "application/json; charset=utf-8")
                .matchHeader("Link", ".*rel=\"next\".*",
                        "<https://api.github.com/search/repositories?q=user%3Astash-test-org+fork%3Atrue&page=2&per_page=5>; rel=\"next\", " +
                                "<https://api.github.com/search/repositories?q=user%3Astash-test-org+fork%3Atrue&page=2&per_page=5>; rel=\"last\"")
                .toFragment();
    }

    @Pact(provider = "github", consumer = "bitbucket_importer_plugin")
    public PactFragment createListRepositoriesLastPageFragment(PactDslWithProvider builder) throws IOException {
        DslPart repositoriesLastPage = new PactDslJsonBody()
                .minArrayLike("items", 5)
                .stringType("clone_url", REPO_CLONE_URL)
                .stringType("description", REPO_DESCRIPTION)
                .stringType("name", REPO_NAME)
                .closeObject();

        return builder
                .given("testing")
                .uponReceiving("List Repositories Last page")
                .path("/search/repositories")
                .headers(ImmutableMap.of(
                        HttpHeaders.AUTHORIZATION, "Basic " + ENCODED_AUTH_STRING))
                .query("q=user:stash-service-user fork:true &page=2&per_page=5")
                .method("GET")
                .willRespondWith()
                .status(200)
                .body(repositoriesLastPage)
                .matchHeader("Content-Type", "application/json; charset=utf-8")
                .matchHeader("Link", "^((?!rel=\"next\").)*$",
                        "<https://api.github.com/search/repositories?q=user%3Astash-test-org+fork%3Atrue&page=1&per_page=5>; rel=\"first\", " +
                                "<https://api.github.com/search/repositories?q=user%3Astash-test-org+fork%3Atrue&page=2&per_page=5>; rel=\"last\"")
                .toFragment();
    }

    @Pact(provider = "github", consumer = "bitbucket_importer_plugin")
    public PactFragment createOAuthScopeFragment(PactDslWithProvider builder) throws IOException {
        return builder
                .given("testing")
                .uponReceiving("OAuth scopes")
                .path("/users/" + GITHUB_USER)
                .headers(ImmutableMap.of(
                        HttpHeaders.AUTHORIZATION, "Basic " + ENCODED_AUTH_STRING))
                .method("HEAD")
                .willRespondWith()
                .status(200)
                .matchHeader("X-OAuth-Scopes", "(\\S+)(,\\s*\\S+)*", "read:org")
                .toFragment();
    }

    @Test
    @PactVerification(value = "github", fragment = "createGetUserFragment")
    public void verifyGetUser() {
        RestAssured
                .given()
                .auth().preemptive().basic(getAdminUser(), getAdminPassword())
                .contentType(MediaType.APPLICATION_JSON)
                .header("Accept", MediaType.APPLICATION_JSON)
                .body(createExternalRequestBody(GITHUB_USER, GITHUB_TOKEN, SourceType.GITHUB.toString()))
                .when()
                .post(getUserUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body("name", equalTo(GITHUB_USER))
                .body("avatarUrl", equalTo(GITHUB_USER_AVATAR_URL));
    }

    @Test
    @PactVerification(value = "github", fragment = "createListOrganizationFragment")
    public void verifyListOrganizations() {
        RestAssured
                .given()
                .auth().preemptive().basic(getAdminUser(), getAdminPassword())
                .contentType(MediaType.APPLICATION_JSON)
                .header("Accept", MediaType.APPLICATION_JSON)
                .body(createExternalRequestBody(GITHUB_USER, GITHUB_TOKEN, SourceType.GITHUB.toString()))
                .queryParam("start", 0)
                .queryParam("limit", 5)
                .when()
                .post(getOwnersUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body("isLastPage", equalTo(true))
                .body("values[0].name", equalTo(GITHUB_ORG))
                .body("values[0].avatarUrl", equalTo(GITHUB_ORG_AVATAR_URL));
    }

    @Test
    @PactVerification(value = "github", fragment = "createListRepositoriesFragment")
    public void verifyListRepositories() {
        RestAssured
                .given()
                .auth().preemptive().basic(getAdminUser(), getAdminPassword())
                .contentType(MediaType.APPLICATION_JSON)
                .header("Accept", MediaType.APPLICATION_JSON)
                .body(createReposBody())
                .queryParam("start", 0)
                .queryParam("limit", 5)
                .when()
                .post(getRepositoriesUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body("isLastPage", equalTo(false))
                .body("values[0].name", equalTo(REPO_NAME))
                .body("values[0].description", equalTo(REPO_DESCRIPTION))
                .body("values[0].cloneUrl", equalTo(REPO_CLONE_URL));
    }

    @Test
    @PactVerification(value = "github", fragment = "createListRepositoriesLastPageFragment")
    public void verifyListRepositoriesLastPage() {
        RestAssured
                .given()
                .auth().preemptive().basic(getAdminUser(), getAdminPassword())
                .contentType(MediaType.APPLICATION_JSON)
                .header("Accept", MediaType.APPLICATION_JSON)
                .body(createReposBody())
                .queryParam("start", 5)
                .queryParam("limit", 5)
                .when()
                .post(getRepositoriesUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body("isLastPage", equalTo(true))
                .body("values[0].name", equalTo(REPO_NAME))
                .body("values[0].description", equalTo(REPO_DESCRIPTION))
                .body("values[0].cloneUrl", equalTo(REPO_CLONE_URL));
    }

    @Test
    @PactVerification(value = "github", fragment = "createOAuthScopeFragment")
    public void verifyMissingOAuthScopes() {
        RestAssured
                .given()
                .auth().preemptive().basic(getAdminUser(), getAdminPassword())
                .contentType(MediaType.APPLICATION_JSON)
                .header("Accept", MediaType.APPLICATION_JSON)
                .body(createExternalRequestBody(GITHUB_USER, GITHUB_TOKEN, SourceType.GITHUB.toString()))
                .when()
                .post(getMissingOAuthScopesUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body("[0]", equalTo(OAuthScope.REPOSITORY.toString()));
    }

    private Map<String, Object> createReposBody() {
        return ImporterConsumerHelper.createReposBody("stash-service-user",
                createCredentialBody(GITHUB_USER, GITHUB_TOKEN),
                createSourceBody(SourceType.GITHUB.toString()));
    }
}
