package com.atlassian.bitbucket.test;

import com.atlassian.bitbucket.async.AsyncTestUtils;
import com.atlassian.bitbucket.async.WaitCondition;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.response.Response;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.hamcrest.Description;

import javax.annotation.Nonnull;
import java.util.Objects;

import static com.atlassian.bitbucket.test.DefaultFuncTestData.getAdminPassword;
import static com.atlassian.bitbucket.test.DefaultFuncTestData.getAdminUser;
import static com.atlassian.bitbucket.test.RestTestHelper.extractValues;
import static javax.ws.rs.core.Response.Status.CREATED;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static javax.ws.rs.core.Response.Status.OK;

/**
 * Test utilities for the creation & deletion of Tags via the REST interface.
 */
public class TagTestHelper {

    /**
     * Creates a LIGHTWEIGHT tag.
     */
    public static void createTag(@Nonnull String projectKey, @Nonnull String repoSlug, @Nonnull String tagName,
                                 @Nonnull String commitId) {
        createTagImpl(projectKey, repoSlug, tagName, commitId, null);
    }

    /**
     * Creates an ANNOTATED tag.
     */
    public static void createTag(@Nonnull String projectKey, @Nonnull String repoSlug, @Nonnull String tagName,
                                 @Nonnull String commitId, @Nonnull String message) {
        Objects.requireNonNull(message, "message");
        createTagImpl(projectKey, repoSlug, tagName, commitId, message);
    }

    /**
     * Deletes the given tag.
     */
    public static void deleteTag(@Nonnull String projectKey, @Nonnull String repoSlug, @Nonnull String tagName) {
        RestAssured.given()
                    .auth()
                        .preemptive()
                        .basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                    .contentType(ContentType.JSON)
                .when()
                    .delete(getGitTagURL(projectKey, repoSlug) + "/" + tagName)
                .then()
                    .log().ifValidationFails()
                    .statusCode(NO_CONTENT.getStatusCode());
    }

    /**
     * Block until the specified tag (on a given commit) is found to be in the Tag index.
     * @throws AssertionError if the tag could not be found after 5 seconds
     */
    public static void waitForTagInIndex(@Nonnull String projectKey, @Nonnull String repoSlug,
                                         @Nonnull String tagName, @Nonnull String commitId) {
        JSONArray json = new JSONArray();
        json.add(commitId);

        long timeoutMs = 5000;
        AsyncTestUtils.waitFor(new WaitCondition() {
            @Override
            public boolean test() throws Exception {
                Response response = RestAssured
                        .given()
                            .auth().preemptive().basic(getAdminUser(), getAdminPassword())
                            .contentType("application/json")
                            .body(json)
                        .expect()
                            .statusCode(OK.getStatusCode())
                            .log().ifError()
                        .when()
                            .post(getTagIndexUrl(projectKey, repoSlug));

                return extractValues(response).stream()
                        .anyMatch(jsonObject -> jsonObject.get("displayId").equals(tagName));
            }

            @Override
            public void describeFailure(Description description) throws Exception {
                description.appendText("Tag ''" + tagName + "'' could not be found after waiting for "
                        + timeoutMs + "ms");
            }
        }, timeoutMs);
    }

    public static JSONObject getGitTagBody(String tagName, String commitId, String message) {
        JSONObject body = new JSONObject();
        body.put("name", tagName);
        body.put("startPoint", commitId);
        if (message == null) {
            body.put("type", "LIGHTWEIGHT");
        } else {
            body.put("message", message);
            body.put("type", "ANNOTATED");
        }
        return body;
    }

    /**
     * Creates an tag, ANNOTATED if message is != null otherwise LIGHTWEIGHT.
     */
    private static void createTagImpl(String projectKey, String repoSlug, String tagName, String commitId, String message) {
        RestAssured.given()
                    .auth()
                        .preemptive()
                        .basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                    .body(getGitTagBody(tagName, commitId, message))
                    .contentType(ContentType.JSON)
                .when()
                    .post(getGitTagURL(projectKey, repoSlug))
                .then()
                    .log().ifValidationFails()
                    .statusCode(CREATED.getStatusCode());
    }

    // When the SCM agnostic tags REST interface supports tag deletion this can be removed in favour of getTagURL()
    private static String getGitTagURL(String projectKey, String repoSlug) {
        return DefaultFuncTestData.getRepositoryRestURL("git", "latest", projectKey, repoSlug) + "/tags";
    }

    private static String getTagIndexUrl(String projectKey, String repoSlug) {
        return DefaultFuncTestData.getRepositoryRestURL("tags", "latest", projectKey, repoSlug) + "/tags";
    }

    // This resource exists, and has a 'create' method, but does not understand some git specific options,
    // such as "force" or "type"
    private static String getTagURL(String projectKey, String repoSlug) {
        return DefaultFuncTestData.getRepositoryRestURL(projectKey, repoSlug) + "/tags";
    }
}
