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

import com.atlassian.bitbucket.test.DefaultFuncTestData;
import com.atlassian.bitbucket.test.RefRestrictionTestHelper;
import com.atlassian.bitbucket.test.RepositoryTestHelper;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.http.ContentType;
import it.com.atlassian.stash.BaseRestTest;
import net.sf.json.JSONObject;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.rules.TestRule;

import java.util.Collections;
import java.util.List;

import static com.atlassian.bitbucket.test.DefaultFuncTestData.*;
import static com.atlassian.bitbucket.test.TestRuleOrderingSyntax.outerRules;
import static javax.ws.rs.core.Response.Status.*;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;

/**
 * Test the branch creation functionality.
 * <p>
 * Resource: {@code com.atlassian.bitbucket.internal.branch.rest.BranchUtilsResource}.
 */
public class BranchCreationResourceTest extends BaseRestTest {

    private static final String PROJECT = '~' + DefaultFuncTestData.getAdminUser();
    private static final String REPO = BranchCreationResourceTest.class.getSimpleName();

    private static String repoLocation;

    @Rule
    public TestName testName = new TestName();

    // Intentionally not annotated with @Rule as it is referred in the RuleChain below
    private RefRestrictionTestHelper restrictedRefs = new RefRestrictionTestHelper(PROJECT, REPO);

    @Override
    protected List<TestRule> getRetryRuleChain() {
        return outerRules(super.getRetryRuleChain()).
               around(restrictedRefs);
    }

    @BeforeClass
    public static void setup() throws Exception {
        //clone repo
        repoLocation =
                RestAssured.given()
                        .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                        .body(RepositoryTestHelper.createRepositoryBody(REPO)).contentType("application/json")
                        .expect().statusCode(CREATED.getStatusCode())
                        .body("name", equalTo(REPO)).body("scmId", equalTo("git"))
                        .when().post(DefaultFuncTestData.getRestURL() + "/projects/" + DefaultFuncTestData.getProject1() + "/repos/" + DefaultFuncTestData.getProject1Repository1()).getHeader("Location");
    }

    @AfterClass
    public static void teardown() throws Exception {
        //delete repo
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .expect().statusCode(ACCEPTED.getStatusCode())
                .when().delete(repoLocation);
    }

    @Test
    public void testBranchCreationFromBranch() {
        String branchName = testName.getMethodName();
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody(branchName, "refs/heads/master"))
                .expect().statusCode(CREATED.getStatusCode())
                .log().ifError()
                .body("displayId", equalTo(branchName))
                .body("latestCommit", equalTo("0a943a29376f2336b78312d99e65da17048951db"))
                .body("isDefault", equalTo(false))
                .when().post(getBranchCreateURL());
    }

    @Test
    public void testBranchCreationFromCommit() {
        String branchName = testName.getMethodName();
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody(branchName, "0a943a29376f2336b78312d99e65da17048951db"))
                .expect().statusCode(CREATED.getStatusCode())
                .log().ifError()
                .body("displayId", equalTo(branchName))
                .body("latestCommit", equalTo("0a943a29376f2336b78312d99e65da17048951db"))
                .body("isDefault", equalTo(false))
                .when().post(getBranchCreateURL());
    }

    @Test
    public void testInvalidBranchNameWithColon() {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody("foo:bar", "refs/heads/master"))
                .expect().statusCode(BAD_REQUEST.getStatusCode())
                .log().ifStatusCodeMatches(not(BAD_REQUEST.getStatusCode()))
                .body("errors[0].context", equalTo("name"))
                .body("errors[0].exceptionName", equalTo("com.atlassian.bitbucket.repository.InvalidRefNameException"))
                .body("errors[0].refName", equalTo("foo:bar"))
                .when().post(getBranchCreateURL());
    }

    /**
     * STASHDEV-5040
     */
    @Test
    public void testInvalidBranchNameWithBackslash() {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody("foo\\bar", "refs/heads/master"))
                .expect().statusCode(BAD_REQUEST.getStatusCode())
                .log().ifStatusCodeMatches(not(BAD_REQUEST.getStatusCode()))
                .body("errors[0].context", equalTo("name"))
                .body("errors[0].exceptionName", equalTo("com.atlassian.bitbucket.repository.InvalidRefNameException"))
                .body("errors[0].refName", equalTo("foo\\bar"))
                .when().post(getBranchCreateURL());
    }

    /**
     * STASHDEV-5053
     */
    @Test
    public void testMaliciousBranchNameWithDoubleHyphen() {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody("-- foo/bar", "refs/heads/master"))
                .expect().statusCode(BAD_REQUEST.getStatusCode())
                .log().ifStatusCodeMatches(not(BAD_REQUEST.getStatusCode()))
                .body("errors[0].context", equalTo("name"))
                .body("errors[0].exceptionName", equalTo("com.atlassian.bitbucket.repository.InvalidRefNameException"))
                .body("errors[0].refName", equalTo("-- foo/bar"))
                .when().post(getBranchCreateURL());
    }

    /**
     * STASHDEV-5041
     */
    @Test
    public void testMaliciousBranchNameWithSingleHyphen() {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody(" -", "refs/heads/master"))
                .expect().statusCode(BAD_REQUEST.getStatusCode())
                .log().ifStatusCodeMatches(not(BAD_REQUEST.getStatusCode()))
                .body("errors[0].context", equalTo("name"))
                .body("errors[0].exceptionName", equalTo("com.atlassian.bitbucket.repository.InvalidRefNameException"))
                .body("errors[0].refName", equalTo(" -"))
                .when().post(getBranchCreateURL());
    }

    @Test
    public void testDuplicateBranch() {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody("master", "refs/heads/master"))
                .expect().statusCode(BAD_REQUEST.getStatusCode())
                .log().ifStatusCodeMatches(not(BAD_REQUEST.getStatusCode()))
                .body("errors[0].context", equalTo("name"))
                .body("errors[0].exceptionName", equalTo("com.atlassian.bitbucket.repository.DuplicateRefException"))
                .body("errors[0].refName", equalTo("master"))
                .when().post(getBranchCreateURL());
    }

    @Test
    public void testConflictingBranch() {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody("master/1.0", "refs/heads/master"))
                .expect().statusCode(CONFLICT.getStatusCode())
                .log().ifStatusCodeMatches(not(CONFLICT.getStatusCode()))
                .body("errors[0].context", equalTo("name"))
                .body("errors[0].exceptionName", equalTo("com.atlassian.bitbucket.repository.ConflictingRefNameException"))
                .body("errors[0].refName", equalTo("master/1.0"))
                .when().post(getBranchCreateURL());
    }

    @Test
    public void testStartPointNotFound() {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody("foobar", "nosuchstartpoint"))
                .expect().statusCode(NOT_FOUND.getStatusCode())
                .log().ifStatusCodeMatches(not(NOT_FOUND.getStatusCode()))
                .body("errors[0].exceptionName", equalTo("com.atlassian.bitbucket.NoSuchObjectException"))
                .when().post(getBranchCreateURL());
    }

    @Test
    public void testInvalidStartPoint() {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody("foobar", "0a943a2:binary/D.zip"))
                .expect().statusCode(BAD_REQUEST.getStatusCode())
                .log().ifStatusCodeMatches(not(BAD_REQUEST.getStatusCode()))
                .body("errors[0].exceptionName", equalTo("com.atlassian.bitbucket.InvalidNameException"))
                .when().post(getBranchCreateURL());
    }

    @Test
    public void testNoWriteAccessToRepository() {
        String branchName = testName.getMethodName();
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getRegularUser(), DefaultFuncTestData.getRegularUserPassword())
                .contentType("application/json")
                .body(createBranchCreationBody(branchName, "0a943a29376f2336b78312d99e65da17048951db"))
                .expect().statusCode(UNAUTHORIZED.getStatusCode())
                .log().ifStatusCodeMatches(not(UNAUTHORIZED.getStatusCode()))
                .body("errors[0].exceptionName", equalTo("com.atlassian.bitbucket.AuthorisationException"))
                .when().post(getBranchCreateURL());
    }

    @Test
    public void testHasBranchRestriction() {
        String branchName = testName.getMethodName();
        restrictedRefs.createRestrictedRef(RefRestrictionTestHelper.RefMatcherType.BRANCH, "refs/heads/" + branchName, RefRestrictionTestHelper.RestrictionType.READ_ONLY, Collections.singleton(DefaultFuncTestData.getRegularUser()), Collections.<String>emptyList());
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType("application/json")
                .body(createBranchCreationBody(branchName, "0a943a29376f2336b78312d99e65da17048951db"))
                .expect().statusCode(BAD_REQUEST.getStatusCode())
                .log().ifStatusCodeMatches(not(BAD_REQUEST.getStatusCode()))
                .body("errors[0].exceptionName", equalTo("com.atlassian.bitbucket.event.branch.BranchCreationCanceledException"))
                .when().post(getBranchCreateURL());
    }

    @Test
    public void testDefaultBranch() {
        RestAssured.
                given().
                auth().preemptive().basic(getAdminUser(), getAdminPassword()).
                header("Accept", ContentType.JSON.getAcceptHeader()).
                expect().
                body("values[0].id", equalTo("refs/heads/master")).
                body("values[0].displayId", equalTo("master")).
                statusCode(OK.getStatusCode()).
                when().
                // This is the first commit, which should _only_ be on master
                        get(getBranchInfoURL(getProject1(), getProject1Repository1(), "409a2180fd95b744df8d817e770b7f8c853f78dc"));
    }

    @Test
    public void testOtherBranch() {
        RestAssured.
                given().
                auth().preemptive().basic(getAdminUser(), getAdminPassword()).
                header("Accept", ContentType.JSON.getAcceptHeader()).
                expect().
                body("values[0].id", equalTo("refs/heads/basic_branching")).
                body("values[0].displayId", equalTo("basic_branching")).
                statusCode(OK.getStatusCode()).
                when().
                get(getBranchInfoURL(getProject1(), getProject1Repository1(), "d6edcbf924697ab811a867421dab60d954ccad99"));
    }

    private JSONObject createBranchCreationBody(String branchName, String startPoint) {
        JSONObject bodyObject = new JSONObject();

        bodyObject.put("name", branchName);
        bodyObject.put("startPoint", startPoint);
        return bodyObject;
    }

    private String getBranchCreateURL() {
        return getBranchURL(PROJECT, REPO);
    }

    private String getBranchInfoURL(String projectKey, String repoSlug, String commitId) {
        return getBranchURL(projectKey, repoSlug) + "/info/" + commitId;
    }

    private String getBranchURL(String projectKey, String repoSlug) {
        return getRepositoryRestURL("branch-utils", "latest", projectKey, repoSlug) + "/branches";
    }
}
