package it.com.atlassian.stash.rest.compare;

import com.atlassian.bitbucket.content.DiffSegmentType;
import com.atlassian.bitbucket.content.DiffWhitespace;
import com.atlassian.bitbucket.hamcrest.RegexMatcher;
import com.atlassian.bitbucket.test.DefaultFuncTestData;
import com.atlassian.bitbucket.test.RepositoryTestHelper;
import com.google.common.collect.ImmutableList;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.filter.log.LogDetail;
import it.com.atlassian.stash.BaseRestTest;
import org.hamcrest.Matcher;
import org.junit.Test;

import java.util.List;

import static com.atlassian.bitbucket.rest.util.RestUtils.isImmutableObjectId;
import static javax.ws.rs.core.Response.Status.*;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;

public class CompareResourceTest extends BaseRestTest {

    private static final String PROJECT = DefaultFuncTestData.getProject1();
    private static final String REPO = DefaultFuncTestData.getProject1Repository1();

    // qualified refs, unqualified refs, full sha and partial shas for the same commits;
    // this is used to check that comparing two commits using any of those identifiers returns
    // the same list of commits, same diff or same list of changed files
    private List<String[]> commitsToCompare = ImmutableList.of(
            new String[] {"refs/heads/basic_branching", "refs/heads/branch_mod_merge"},
            new String[] {"basic_branching", "branch_mod_merge"},
            new String[] {"d6edcbf924697ab811a867421dab60d954ccad99", "6053a1eaa1c009dd11092d09a72f3c41af1b59ad"},
            new String[] {"d6edcbf", "6053a1e"});

    @Test
    public void testGetCommits() {
        for (String[] commits : commitsToCompare) {
            String from = commits[0];
            String to = commits[1];
            RestAssured
                    .expect()
                        .statusCode(OK.getStatusCode())
                        .body("isLastPage", equalTo(true))
                        .body("size", equalTo(1))
                        .body("start", equalTo(0))
                        .body("values[0].id", equalTo("d6edcbf924697ab811a867421dab60d954ccad99"))
                        .body("values[0].author.name", equalTo("Michael Heemskerk"))
                        .header("Cache-Control", "no-cache, no-transform")
                        .log().ifValidationFails()
                    .given()
                        .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                        .contentType("application/json")
                    .when()
                        .queryParam("from", from)
                        .queryParam("to", to)
                        .get(compareUrl("commits"));
        }
    }

    @Test
    public void testGetCommitsForAnonymousUser() {
        String project = PROJECT;
        String repository = REPO;
        RepositoryTestHelper.setRepositoryPublicAccessEnabled(project, repository, true);
        try {
            for (String[] commits : commitsToCompare) {
                String from = commits[0];
                String to = commits[1];
                RestAssured
                        .expect()
                            .statusCode(OK.getStatusCode())
                            .body("isLastPage", equalTo(true))
                            .body("size", equalTo(1))
                            .body("start", equalTo(0))
                            .body("values[0].id", equalTo("d6edcbf924697ab811a867421dab60d954ccad99"))
                            .body("values[0].author.name", equalTo("Michael Heemskerk"))
                            .header("Cache-Control", "no-cache, no-transform")
                            .log().ifValidationFails()
                        .given()
                            .contentType("application/json")
                        .when()
                            .queryParam("from", from)
                            .queryParam("to", to)
                            .get(compareUrl(project, repository, "commits"));
            }
        } finally {
            RepositoryTestHelper.setRepositoryPublicAccessEnabled(project, repository, false);
        }
    }

    @Test
    public void testGetCommitsForAnonymousUserOnNonPublicRepository() {
        for (String[] commits : commitsToCompare) {
            String from = commits[0];
            String to = commits[1];
            RestAssured
                    .expect()
                        .statusCode(UNAUTHORIZED.getStatusCode())
                        .log().ifValidationFails()
                    .given()
                        .contentType("application/json")
                    .when()
                        .queryParam("from", from)
                        .queryParam("to", to)
                        .get(compareUrl("commits"));
        }
    }

    @Test
    public void testGetCommitsWithNonexistentRepository() {
        String[] commits = commitsToCompare.get(0);
        String project = PROJECT;
        String repository = "non-existent-repo";
        RestAssured
                .expect()
                    .statusCode(NOT_FOUND.getStatusCode())
                    .body("errors[0].message", containsString(String.format("Repository %s/%s does not exist", project, repository)))
                    .log().ifValidationFails()
                .given()
                    .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                    .contentType("application/json")
                .when()
                    .queryParam("from", commits[0])
                    .queryParam("to", commits[1])
                    .get(compareUrl(project, repository, "commits"));
    }

    @Test
    public void testGetCommitsWithNonexistentSourceRepository() {
        String[] commits = commitsToCompare.get(0);
        String project = PROJECT;
        String repository = "non-existent-repo";
        RestAssured
                .expect()
                    .statusCode(NOT_FOUND.getStatusCode())
                    .body("errors[0].message", containsString(String.format("The specified secondary repository, %s/%s, does not exist.", project, repository)))
                    .log().ifValidationFails()
                .given()
                    .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                    .contentType("application/json")
                .when()
                    .queryParam("from", commits[0])
                    .queryParam("to", commits[1])
                    .queryParam("fromRepo", PROJECT + '/' + repository)
                    .get(compareUrl("commits"));
    }

    @Test
    public void testGetCommitsWithAmbiguousSourceCommit() {
        RestAssured
                .expect()
                    .statusCode(NOT_FOUND.getStatusCode())
                    .body("errors[0].message", matchErrorMissingCommit("d6"))
                    .log().ifStatusCodeMatches(not(equalTo(NOT_FOUND.getStatusCode())))
                .given()
                    .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                    .contentType("application/json")
                .when()
                    .queryParam("from", "d6")
                    .queryParam("to", "6053a1eaa1c009dd11092d09a72f3c41af1b59ad")
                    .get(compareUrl("commits"));
    }

    @Test
    public void testGetCommitsWithNonexistentSourceCommit() {
        String[] commits = commitsToCompare.get(0);
        RestAssured
                .expect()
                    .statusCode(NOT_FOUND.getStatusCode())
                    .body("errors[0].message", matchErrorMissingCommit("123abc"))
                    .log().ifValidationFails()
                .given()
                    .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                    .contentType("application/json")
                .when()
                    .queryParam("from", "123abc")
                    .queryParam("to", commits[1])
                .get(compareUrl("commits"));
    }

    @Test
    public void testGetCommitsWithNonexistentTargetCommit() {
        String[] commits = commitsToCompare.get(0);
        RestAssured
                .expect()
                    .statusCode(NOT_FOUND.getStatusCode())
                    .body("errors[0].message", matchErrorMissingCommit("456def"))
                    .log().ifValidationFails()
                .given()
                    .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                    .contentType("application/json")
                .when()
                    .queryParam("from", commits[0])
                    .queryParam("to", "456def")
                .get(compareUrl("commits"));
    }

    @Test
    public void testGetChanges() {
        for (String[] commits : commitsToCompare) {
            String from = commits[0];
            String to = commits[1];
            RestAssured
                    .expect()
                        .statusCode(OK.getStatusCode())
                        .body("fromHash", equalTo("8fb8538799b2481750eac52716931d895d029b7c")) // merge-base -> always a SHA1
                        .body("toHash", equalTo(from))
                        .body("isLastPage", equalTo(true))
                        .body("start", equalTo(0))
                        .body("size", equalTo(1))
                        .body("values[0].contentId", equalTo("94efe92828e29523d68baaff145f81a680bacad4"))
                        .body("values[0].nodeType", equalTo("FILE"))
                        .body("values[0].path.components[0]", equalTo("basic_branching"))
                        .body("values[0].path.components[1]", equalTo("file.txt"))
                        .body("values[0].type", equalTo("MODIFY"))
                        .header("Cache-Control", isImmutableObjectId(from) && isImmutableObjectId(to) ?
                                containsString("max-age=2419200") : equalTo("no-cache, no-transform"))
                        .log().ifValidationFails()
                    .given()
                        .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                        .contentType("application/json")
                    .when()
                        .queryParam("from", from)
                        .queryParam("to", to)
                        .get(compareUrl("changes"));
        }
    }

    @Test
    public void testGetDiff() {
        for (String[] commits : commitsToCompare) {
            String from = commits[0];
            String to = commits[1];
            String file = "basic_branching/file.txt";
            RestAssured
                    .expect()
                        .statusCode(OK.getStatusCode())
                        .body("fromHash", equalTo("8fb8538799b2481750eac52716931d895d029b7c"))
                        .body("toHash", equalTo(from))
                        .body("contextLines", equalTo(10)) // check default
                        .body("whitespace", equalTo(DiffWhitespace.SHOW.name())) // check default
                        .body("diffs.size()", equalTo(1))
                        .body("diffs[0].source.toString", equalTo(file))
                        .body("diffs[0].destination.toString", equalTo(file))
                        .body("diffs[0].truncated", equalTo(false))
                        .body("diffs[0].hunks.size()", equalTo(1))
                        .body("diffs[0].hunks[0].sourceLine", equalTo(1))
                        .body("diffs[0].hunks[0].sourceSpan", equalTo(1))
                        .body("diffs[0].hunks[0].destinationLine", equalTo(1))
                        .body("diffs[0].hunks[0].destinationSpan", equalTo(1))
                        .body("diffs[0].hunks[0].truncated", equalTo(false))
                        .body("diffs[0].hunks[0].segments.size()", equalTo(2))
                        .body("diffs[0].hunks[0].segments[0].type", equalTo(DiffSegmentType.REMOVED.name()))
                        .body("diffs[0].hunks[0].segments[0].truncated", equalTo(false))
                        .body("diffs[0].hunks[0].segments[0].lines.size()", equalTo(1))
                        .body("diffs[0].hunks[0].segments[0].lines[0].source", equalTo(1))
                        .body("diffs[0].hunks[0].segments[0].lines[0].destination", equalTo(1))
                        .body("diffs[0].hunks[0].segments[0].lines[0].line", equalTo("basic branching scenario"))
                        .body("diffs[0].hunks[0].segments[0].lines[0].truncated", equalTo(false))
                        .body("diffs[0].hunks[0].segments[1].type", equalTo(DiffSegmentType.ADDED.name()))
                        .body("diffs[0].hunks[0].segments[1].truncated", equalTo(false))
                        .body("diffs[0].hunks[0].segments[1].lines.size()", equalTo(1))
                        .body("diffs[0].hunks[0].segments[1].lines[0].source", equalTo(2))
                        .body("diffs[0].hunks[0].segments[1].lines[0].destination", equalTo(1))
                        .body("diffs[0].hunks[0].segments[1].lines[0].line", equalTo("a modification on branch basic_branching"))
                        .body("diffs[0].hunks[0].segments[1].lines[0].truncated", equalTo(false))
                        .log().ifValidationFails(LogDetail.BODY)
                    .given()
                        .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                        .contentType("application/json")
                    .when()
                        .queryParam("from", from)
                        .queryParam("to", to)
                        .get(compareUrl("diff") + '/' + file);
        }
    }

    @Test
    public void testGetDiffWithFilter() {
        RestAssured
                .expect()
                .statusCode(OK.getStatusCode())
                .body("diffs[0].hunks.size()", equalTo(2))
                .body("diffs[0].hunks[0].segments.size()", equalTo(1))
                .body("diffs[0].hunks[1].segments.size()", equalTo(1))
                .log().ifValidationFails()
                .given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .when()
                .queryParam("from", commitsToCompare.get(0)[0])
                .queryParam("to", commitsToCompare.get(0)[1])
                .get(compareUrl("diff") + "?filter=basic");
        RestAssured
                .expect()
                .statusCode(OK.getStatusCode())
                .body("diffs[0].hunks.size()", equalTo(1))
                .body("diffs[0].hunks[0].segments.size()", equalTo(1))
                .log().ifValidationFails()
                .given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .when()
                .queryParam("from", commitsToCompare.get(0)[0])
                .queryParam("to", commitsToCompare.get(0)[1])
                .get(compareUrl("diff") + "?filter=modification");
    }

    @Test
    public void testGetDiffWithoutPath() {
        for (String[] commits : commitsToCompare) {
            String from = commits[0];
            String to = commits[1];
            RestAssured
                    .expect()
                        .statusCode(OK.getStatusCode())
                        .body("diffs.size()", equalTo(1))
                        .log().ifValidationFails()
                    .given()
                        .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                        .contentType("application/json")
                    .when()
                        .queryParam("from", from)
                        .queryParam("to", to)
                        .get(compareUrl("diff"));
        }
    }

    private static String compareUrl(String section) {
        return compareUrl(PROJECT, REPO, section);
    }

    private static String compareUrl(String project, String repository, String section) {
        return DefaultFuncTestData.getRepositoryRestURL(project, repository) + "/compare/" + section;
    }

    private static Matcher<String> matchErrorMissingCommit(String commitId) {
        return RegexMatcher.matchesRegex(commitId + ".+does not exist");
    }
}
