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.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 BitbucketConsumerTest {

    private static final String MOCK_BITBUCKET_CLOUD_API_HOST = System.getProperty("pact.provider.host", "localhost");
    private static final int MOCK_BITBUCKET_CLOUD_API_PORT = Integer.parseInt(System.getProperty("pact.provider.port", "9234"));

    private static final String BITBUCKET_CLOUD_USER = System.getProperty("bitbucketcloud.user", "<Bitbucket username>");
    private static final String BITBUCKET_CLOUD_TOKEN = System.getProperty("bitbucketcloud.token", "<Bitbucket app password>");

    private static final String BITBUCKET_CLOUD_TEAM = "stash-test-team";
    private static final String BITBUCKET_CLOUD_ORG_AVATAR_URL = "https://bitbucket.org/account/stash-test-team/avatar/32/";

    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 BITBUCKET_CLOUD_USER_AVATAR_URL = "https://bitbucket.org/account/stash-service-user/avatar/32/";

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

    @Rule
    public PactProviderRule mockProvider = new PactProviderRule("bitbucket-cloud", MOCK_BITBUCKET_CLOUD_API_HOST,
            MOCK_BITBUCKET_CLOUD_API_PORT, false, PactConfig.apply(PactSpecVersion.V3), this);

    @Pact(provider = "bitbucket-cloud", consumer = "bitbucket_importer_plugin")
    public PactFragment createGetUserFragment(PactDslWithProvider builder) throws IOException {
        DslPart user = addAvatar(new PactDslJsonBody()
                .stringType("username", BITBUCKET_CLOUD_USER)
                .stringType("type", "user"));

        return builder
                .given("testing")
                .uponReceiving("Get user")
                .path("/users/" + BITBUCKET_CLOUD_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 = "bitbucket-cloud", consumer = "bitbucket_importer_plugin")
    public PactFragment createListOrganizationFragment(PactDslWithProvider builder) throws IOException {
        DslPart owners = new PactDslJsonBody()
                .integerType("page", 1)
                .integerType("pagelen", 1)
                .integerType("size", 1)
                .stringType("next", null)
                .minArrayLike("values", 1)
                .stringType("username", BITBUCKET_CLOUD_TEAM)
                .stringType("type", "team")
                .stringType("display_name", "stash test org")
                .object("links")
                .object("avatar")
                .stringType("href", BITBUCKET_CLOUD_ORG_AVATAR_URL)
                .closeObject()
                .closeObject()
                .closeObject();

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

    @Pact(provider = "bitbucket-cloud", consumer = "bitbucket_importer_plugin")
    public PactFragment createListRepositoriesFragment(PactDslWithProvider builder) throws IOException {
        DslPart repositories = new PactDslJsonBody()
                .integerType("page", 1)
                .integerType("pagelen", 1)
                .integerType("size", 1)
                .stringType("next", "next-url")
                .minArrayLike("values", 5)
                .stringType("scm", "git")
                .stringType("name", REPO_NAME)
                .stringType("description", REPO_DESCRIPTION)
                .object("links")
                .object("avatar")
                .stringType("href", BITBUCKET_CLOUD_USER_AVATAR_URL)
                .closeObject()
                .minArrayLike("clone", 1)
                .stringType("name", "https")
                .stringType("href", REPO_CLONE_URL)
                .closeObject()
                .closeArray()
                .closeObject();

        return builder
                .given("testing")
                .uponReceiving("List Repositories")
                .path("/repositories/" + BITBUCKET_CLOUD_TEAM)
                .headers(ImmutableMap.of(
                        HttpHeaders.AUTHORIZATION, "Basic " + ENCODED_AUTH_STRING))
                .query("q=scm = \"git\"&page=1&pagelen=5")
                .method("GET")
                .willRespondWith()
                .status(200)
                .body(repositories)
                .matchHeader("Content-Type", "application/json; charset=utf-8")
                .toFragment();
    }

    @Pact(provider = "bitbucket-cloud", consumer = "bitbucket_importer_plugin")
    public PactFragment createListRepositoriesLastPageFragment(PactDslWithProvider builder) throws IOException {
        DslPart repositoriesLastPage = new PactDslJsonBody()
                .integerType("page", 2)
                .integerType("pagelen", 1)
                .integerType("size", 1)
                .stringType("next", null)
                .minArrayLike("values", 2)
                .stringType("scm", "git")
                .stringType("name", REPO_NAME)
                .stringType("description", REPO_DESCRIPTION)
                .object("links")
                .object("avatar")
                .stringType("href", BITBUCKET_CLOUD_USER_AVATAR_URL)
                .closeObject()
                .minArrayLike("clone", 1)
                .stringType("name", "https")
                .stringType("href", REPO_CLONE_URL)
                .closeObject()
                .closeArray()
                .closeObject();

        return builder
                .given("testing")
                .uponReceiving("List Repositories Last page")
                .path("/repositories/" + BITBUCKET_CLOUD_TEAM)
                .headers(ImmutableMap.of(
                        HttpHeaders.AUTHORIZATION, "Basic " + ENCODED_AUTH_STRING))
                .query("q=scm = \"git\"&page=2&pagelen=5")
                .method("GET")
                .willRespondWith()
                .status(200)
                .body(repositoriesLastPage)
                .matchHeader("Content-Type", "application/json; charset=utf-8")
                .toFragment();
    }

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

    @Test
    @PactVerification(value = "bitbucket-cloud", fragment = "createGetUserFragment")
    public void verifyGetUser() {
        RestAssured
                .given()
                .auth().preemptive().basic(getAdminUser(), getAdminPassword())
                .contentType(MediaType.APPLICATION_JSON)
                .header("Accept", MediaType.APPLICATION_JSON)
                .body(createExternalRequestBody(BITBUCKET_CLOUD_USER, BITBUCKET_CLOUD_TOKEN, SourceType.BITBUCKET_CLOUD.toString()))
                .when()
                .post(getUserUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body("name", equalTo(BITBUCKET_CLOUD_USER))
                .body("avatarUrl", equalTo(BITBUCKET_CLOUD_USER_AVATAR_URL));
    }

    @Test
    @PactVerification(value = "bitbucket-cloud", fragment = "createListOrganizationFragment")
    public void verifyListOrganizations() {
        RestAssured
                .given()
                .auth().preemptive().basic(getAdminUser(), getAdminPassword())
                .contentType(MediaType.APPLICATION_JSON)
                .header("Accept", MediaType.APPLICATION_JSON)
                .body(createExternalRequestBody(BITBUCKET_CLOUD_USER, BITBUCKET_CLOUD_TOKEN, SourceType.BITBUCKET_CLOUD.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(BITBUCKET_CLOUD_TEAM))
                .body("values[0].avatarUrl", equalTo(BITBUCKET_CLOUD_ORG_AVATAR_URL));
    }

    @Test
    @PactVerification(value = "bitbucket-cloud", 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 = "bitbucket-cloud", 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 = "bitbucket-cloud", fragment = "createOAuthScopeFragment")
    public void verifyMissingOAuthScopes() {
        RestAssured
                .given()
                .auth().preemptive().basic(getAdminUser(), getAdminPassword())
                .contentType(MediaType.APPLICATION_JSON)
                .header("Accept", MediaType.APPLICATION_JSON)
                .body(createExternalRequestBody(BITBUCKET_CLOUD_USER, BITBUCKET_CLOUD_TOKEN, SourceType.BITBUCKET_CLOUD.toString()))
                .when()
                .post(getMissingOAuthScopesUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body("[0]", equalTo(OAuthScope.REPOSITORY.toString()));
    }

    private DslPart addAvatar(PactDslJsonBody body) {
        return body
                .object("links")
                .object("avatar")
                .stringType("href", BITBUCKET_CLOUD_USER_AVATAR_URL)
                .closeObject()
                .closeObject();
    }

    private Map<String, Object> createReposBody() {
        return ImporterConsumerHelper.createReposBody(BITBUCKET_CLOUD_TEAM,
                createCredentialBody(BITBUCKET_CLOUD_USER, BITBUCKET_CLOUD_TOKEN),
                createSourceBody(SourceType.BITBUCKET_CLOUD.toString()));
    }
}
