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

import com.atlassian.bitbucket.test.BranchModelTestHelper;
import com.atlassian.bitbucket.test.RepositoryTestHelper;
import com.atlassian.bitbucket.permission.Permission;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
import it.com.atlassian.stash.BaseRestTest;
import org.apache.commons.lang3.SystemUtils;
import org.junit.After;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import javax.ws.rs.core.Response;
import java.io.IOException;

import static com.atlassian.bitbucket.test.BranchModelTestHelper.*;
import static com.atlassian.bitbucket.test.DefaultFuncTestData.*;
import static com.atlassian.bitbucket.test.rest.RefJsonMatchers.hasDisplayId;
import static com.atlassian.bitbucket.test.rest.ResponseMatchers.*;
import static com.jayway.restassured.RestAssured.withArgs;
import static org.hamcrest.Matchers.*;

public class AutoMergeResourceTest extends BaseRestTest {

    private static final String AUTOMERGE_PATH = "/automerge";
    private static final String AUTOMERGE_ENABLE_PATH = AUTOMERGE_PATH + "/enabled";
    private static final String AUTOMERGE_MERGEPATH_PATH = AUTOMERGE_PATH + "/path";

    private static final String REPO_SLUG = "automerge";
    private static final Resource REPO_ZIP = new ClassPathResource(RepositoryTestHelper.QA_RESOURCES_REPOSITORY);

    private static final String USER_READ = "readuser";
    private static final String USER_WRITE = "writeuser";
    private static final String USER_NOACCESS = "noaccess";

    private final BranchModelTestHelper branchModelTestHelper = new BranchModelTestHelper(getProject1(), REPO_SLUG);

    @BeforeClass
    public static void createRepos() throws IOException {
        Assume.assumeFalse("Does not work on Windows - STASHDEV-5190", SystemUtils.IS_OS_WINDOWS);
        getClassTestContext().repository(getProject1(), REPO_SLUG, REPO_ZIP);
        getClassTestContext().user(USER_READ).repositoryPermission(getProject1(), REPO_SLUG, USER_READ, Permission.REPO_READ);
        getClassTestContext().user(USER_WRITE).repositoryPermission(getProject1(), REPO_SLUG, USER_WRITE, Permission.REPO_WRITE);
        getClassTestContext().user(USER_NOACCESS);
    }

    @After
    public void removeBranchModel() {
        branchModelTestHelper.resetToDefault();
        branchModelTestHelper.disable();
        disableAutoMerge();
    }

    @Test
    public void testEnabled() {
        assertAutoMergeOff();
        enableAutoMerge();
        assertAutoMergeOn();
        disableAutoMerge();
        assertAutoMergeOff();
    }

    @Test
    public void testGetEnabledReadUser() {
        enableAutoMerge();
        RestAssured.expect().given().auth().preemptive().basic(USER_READ, USER_READ)
                .contentType(ContentType.JSON)
                .expect().statusCode(noContent())
                .log().ifStatusCodeMatches(not(noContent()))
                .get(getPath(AUTOMERGE_ENABLE_PATH));
    }

    @Test
    public void testGetEnabledNoAccess() {
        RestAssured.expect().given().auth().preemptive().basic(USER_NOACCESS, USER_NOACCESS)
                .contentType(ContentType.JSON)
                .expect().statusCode(unauthorized())
                .log().ifStatusCodeMatches(not(unauthorized()))
                .get(getPath(AUTOMERGE_ENABLE_PATH));
    }

    @Test
    public void testPutEnabledNonAdmin() {
        RestAssured.expect().given().auth().preemptive().basic(USER_WRITE, USER_WRITE)
                .contentType(ContentType.JSON)
                .expect().statusCode(unauthorized())
                .log().ifStatusCodeMatches(not(unauthorized()))
                .put(getPath(AUTOMERGE_ENABLE_PATH));
    }

    @Test
    public void testExistingMergePath() {
        createBranchModelWithMatchingReleaseBranches();
        enableAutoMerge();
        okRest().expect()
                .body("status.available", is(true))
                .body("path", hasSize(20))
                .body("path[%d]", withArgs(0), hasDisplayId("branch1"))
                .body("path[%d]", withArgs(1), hasDisplayId("branch2"))
                .body("path[%d]", withArgs(2), hasDisplayId("branch3"))
                        // BRANCH11 not included - we match prefixes case sensitive
                .body("path[%d]", withArgs(18), hasDisplayId("branch20"))
                .body("path[%d]", withArgs(19), hasDisplayId("master"))
                .get(getPath(AUTOMERGE_MERGEPATH_PATH + "?branchRefId=refs/heads/branch1"));
    }

    @Test
    public void testExistingMergePathNonFull() {
        createBranchModelWithMatchingReleaseBranches();
        enableAutoMerge();
        okRest().expect()
                .body("status.available", is(true))
                .body("path", hasSize(7))
                .body("path[%d]", withArgs(0), hasDisplayId("branch15"))
                .body("path[%d]", withArgs(1), hasDisplayId("branch16"))
                .body("path[%d]", withArgs(2), hasDisplayId("branch17"))
                .body("path[%d]", withArgs(3), hasDisplayId("branch18"))
                .body("path[%d]", withArgs(4), hasDisplayId("branch19"))
                .body("path[%d]", withArgs(5), hasDisplayId("branch20"))
                .body("path[%d]", withArgs(6), hasDisplayId("master"))
                .get(getPath(AUTOMERGE_MERGEPATH_PATH + "?branchRefId=refs/heads/branch15"));
    }

    @Test
    public void testNoBranchRefId() {
        branchModelTestHelper.enable();
        enableAutoMerge();
        rest().expect()
                .statusCode(badRequest())
                .log().ifStatusCodeMatches(not(badRequest()))
                .get(getPath(AUTOMERGE_MERGEPATH_PATH));
    }

    @Test
    public void testWrongBranchRefId() {
        branchModelTestHelper.enable();
        enableAutoMerge();
        rest().expect()
                .statusCode(badRequest())
                .log().ifStatusCodeMatches(not(badRequest()))
                .get(getPath(AUTOMERGE_MERGEPATH_PATH + "?branchRefId=refs/heads/notexistingbranch"));
    }

    @Test
    public void testMergePathBranchModelDisabled() {
        enableAutoMerge();
        okRest().expect()
                .body("status.available", is(false))
                .body("status.id", is("CANNOT_CALCULATE_PATH"))
                .get(getPath(AUTOMERGE_MERGEPATH_PATH + "?branchRefId=refs/heads/branch1"));
    }

    @Test
    public void testMergePathAutoMergeDisabled() {
        createBranchModelWithMatchingReleaseBranches();
        okRest().expect()
                .body("status.available", is(false))
                .body("status.id", is("AUTO_MERGE_DISABLED"))
                .get(getPath(AUTOMERGE_MERGEPATH_PATH + "?branchRefId=refs/heads/branch1"));
    }

    @Test
    public void testMergePathBranchWithNoPath() {
        // release/ prefix by default - won't match any branch
        branchModelTestHelper.enable();
        enableAutoMerge();
        okRest().expect()
                .body("status.available", is(false))
                .body("status.id", is("NO_PATH"))
                .get(getPath(AUTOMERGE_MERGEPATH_PATH + "?branchRefId=refs/heads/branch1"));
    }

    @Test
    public void testMergePathNonAdmin() {
        branchModelTestHelper.enable();
        enableAutoMerge();
        RestAssured.expect().given().auth().preemptive().basic(USER_READ, USER_READ)
                .contentType(ContentType.JSON)
                .expect().statusCode(ok())
                .log().ifStatusCodeMatches(not(ok()))
                .get(getPath(AUTOMERGE_MERGEPATH_PATH + "?branchRefId=refs/heads/branch1"));
    }

    @Test
    public void testGetMergePathNoAccess() {
        RestAssured.expect().given().auth().preemptive().basic(USER_NOACCESS, USER_NOACCESS)
                .contentType(ContentType.JSON)
                .expect().statusCode(unauthorized())
                .log().ifStatusCodeMatches(not(unauthorized()))
                .get(getPath(AUTOMERGE_MERGEPATH_PATH));
    }

    private void createBranchModelWithMatchingReleaseBranches() {
        branchModelTestHelper.enable(createBranchModelJson(createBranchConfiguration(null, true), null,
                createBranchTypeConfiguration(BUGFIX_TYPE, "bugfix/", true),
                createBranchTypeConfiguration(RELEASE_TYPE, "branch", true))
        );
    }

    private void assertAutoMergeOff() {
        statusRest(Response.Status.NOT_FOUND).get(getPath(AUTOMERGE_ENABLE_PATH));
    }

    private void assertAutoMergeOn() {
        statusRest(Response.Status.NO_CONTENT).get(getPath(AUTOMERGE_ENABLE_PATH));
    }

    private void disableAutoMerge() {
        okRest().delete(getPath(AUTOMERGE_ENABLE_PATH));
    }

    private void enableAutoMerge() {
        okRest().put(getPath(AUTOMERGE_ENABLE_PATH));
    }

    private String getPath(String path) {
        return getRepositoryRestURL("branch-utils", "latest", getProject1(), REPO_SLUG) + path;
    }

    private static ResponseSpecification okRest() {
        return rest().expect().statusCode(successful())
                .log().ifError();
    }

    private static RequestSpecification rest() {
        return RestAssured.expect().given().auth().preemptive().basic(getAdminUser(), getAdminPassword())
                .contentType(ContentType.JSON);
    }

    private static ResponseSpecification statusRest(Response.Status status) {
        return rest().expect().statusCode(status.getStatusCode())
                .log().ifStatusCodeMatches(not(status.getStatusCode()));
    }
}
