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.atlassian.bitbucket.permission.Permission;
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.BeforeClass;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;

import java.util.Collections;

import static com.atlassian.bitbucket.test.DefaultFuncTestData.*;
import static javax.ws.rs.core.Response.Status.*;
import static org.junit.Assert.assertEquals;

/**
 * Test the branch deletion functionality.
 * <p>
 * Resource: {@code com.atlassian.bitbucket.internal.branch.rest.BranchUtilsResource}.
 *
 * @see it.com.atlassian.stash.rest.pull.PullRequestBranchDeletionResourceTest
 */
public class BranchDeletionResourceTest extends BaseRestTest {

    private static final String REPOSITORY = "branch-deletion-tests";
    private static final String USER_WITH_READ_ACCESS  = "readaccess";
    private static final String USER_WITH_WRITE_ACCESS = "writeaccess";

    @BeforeClass
    public static void setUp() throws Exception {
        getClassTestContext().repository(getProject1(), REPOSITORY, new ClassPathResource(RepositoryTestHelper.DEFAULT_REPOSITORY))
                   .user(USER_WITH_READ_ACCESS)
                   .user(USER_WITH_WRITE_ACCESS)
                   .repositoryPermission(getProject1(), REPOSITORY, USER_WITH_READ_ACCESS, Permission.REPO_READ)
                   .repositoryPermission(getProject1(), REPOSITORY, USER_WITH_WRITE_ACCESS, Permission.REPO_WRITE);
    }

    @Test
    public void testDeleteBranch() throws Exception {
        assertWhetherBranchExists("branch_mod_merge", true);
        RestAssured.given()
                .auth().preemptive().basic(USER_WITH_WRITE_ACCESS, USER_WITH_WRITE_ACCESS)
                .contentType(ContentType.JSON)
                .body(createBranchDeletionBody("branch_mod_merge", false))
                .expect()
                .statusCode(NO_CONTENT.getStatusCode())
                .log().ifError()
                .when()
                .delete(getBranchDeleteURL());
        assertWhetherBranchExists("branch_mod_merge", false);
    }

    @Test
    public void testDryRunDeleteBranch() throws Exception {
        assertWhetherBranchExists("basic_branching", true);
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType(ContentType.JSON)
                .body(createBranchDeletionBody("basic_branching", true))
                .expect()
                .statusCode(NO_CONTENT.getStatusCode())
                .log().ifError()
                .when()
                .delete(getBranchDeleteURL());
        assertWhetherBranchExists("basic_branching", true);
    }

    @Test
    public void testTryDeletingNonExistentBranch() throws Exception {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType(ContentType.JSON)
                .body(createBranchDeletionBody("non-existent-branch", false))
                .expect()
                .statusCode(NO_CONTENT.getStatusCode()) // the end point is idempotent
                .log().ifError()
                .when()
                .delete(getBranchDeleteURL());
    }

    @Test
    public void testTryDeletingBranchWithoutProvidingItsName() throws Exception {
        RestAssured.given()
                .auth().preemptive().basic(USER_WITH_READ_ACCESS, USER_WITH_READ_ACCESS)
                .contentType(ContentType.JSON)
                .body(createBranchDeletionBody("", false))
                .expect()
                .statusCode(BAD_REQUEST.getStatusCode())
                .when()
                .delete(getBranchDeleteURL());
        RestAssured.given()
                .auth().preemptive().basic(USER_WITH_READ_ACCESS, USER_WITH_READ_ACCESS)
                .contentType(ContentType.JSON)
                .body(createBranchDeletionBody("   ", false))
                .expect()
                .statusCode(BAD_REQUEST.getStatusCode())
                .when()
                .delete(getBranchDeleteURL());
    }

    @Test
    public void testTryDeletingWithoutWritePermission() throws Exception {
        RestAssured.given()
                .auth().preemptive().basic(USER_WITH_READ_ACCESS, USER_WITH_READ_ACCESS)
                .contentType(ContentType.JSON)
                .body(createBranchDeletionBody("basic_branching", false))
                .expect()
                .statusCode(UNAUTHORIZED.getStatusCode())
                .when()
                .delete(getBranchDeleteURL());
        assertWhetherBranchExists("basic_branching", true);
    }

    @Test
    public void testTryDeletingWithBranchRestriction() throws Exception {
        RefRestrictionTestHelper restrictedRefs = new RefRestrictionTestHelper(DefaultFuncTestData.getProject1(), REPOSITORY);
        restrictedRefs.createRestrictedRef(RefRestrictionTestHelper.RefMatcherType.BRANCH, "refs/heads/basic_branching",
                RefRestrictionTestHelper.RestrictionType.READ_ONLY,
                Collections.singleton(getAdminUser()),
                Collections.<String>emptySet());
        try {
            RestAssured.given()
                    .auth().preemptive().basic(USER_WITH_WRITE_ACCESS, USER_WITH_WRITE_ACCESS)
                    .contentType(ContentType.JSON)
                    .body(createBranchDeletionBody("basic_branching", false))
                    .expect()
                    .statusCode(BAD_REQUEST.getStatusCode())
                    .when()
                    .delete(getBranchDeleteURL());
            assertWhetherBranchExists("basic_branching", true);
        } finally {
            restrictedRefs.deleteAll();
        }
    }

    @Test
    public void testTryDeletingTheDefaultBranch() throws Exception {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType(ContentType.JSON)
                .body(createBranchDeletionBody("master", false))
                .expect()
                .statusCode(BAD_REQUEST.getStatusCode())
                .when()
                .delete(getBranchDeleteURL());
        assertWhetherBranchExists("master", true);
    }

    @Test
    public void testTryDeletingABranchThatHasMoved() throws Exception {
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .contentType(ContentType.JSON)
                .body(createBranchDeletionBody("basic_branching", "1144deccce8fd5763c269afa87ac766ad45cb6c4", false))
                .expect()
                .statusCode(BAD_REQUEST.getStatusCode())
                .when()
                .delete(getBranchDeleteURL());
        assertWhetherBranchExists("basic_branching", true);
    }

    private static void assertWhetherBranchExists(String branchName, boolean exists) {
        assertEquals("branch '" + branchName + "' should " + (exists ? "" : "not ") + "exist",
                exists, RepositoryTestHelper.hasBranch(getProject1(), REPOSITORY, branchName));
    }

    private static String createBranchDeletionBody(String branchName, boolean dryRun) {
        return createBranchDeletionBody(branchName, null, dryRun);
    }

    private static String createBranchDeletionBody(String branchName, String commitId, boolean dryRun) {
        JSONObject json = new JSONObject();
        json.put("name", branchName);
        json.put("dryRun", dryRun);
        if (commitId != null) {
            json.put("endPoint", commitId);
        }
        return json.toString();
    }

    private static String getBranchDeleteURL() {
        return getRepositoryRestURL("branch-utils", "latest", getProject1(), REPOSITORY) + "/branches";
    }

}
