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

import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.pull.PullRequestState;
import com.atlassian.bitbucket.test.DefaultFuncTestData;
import com.atlassian.bitbucket.test.MergeStrategiesTestHelper;
import com.atlassian.bitbucket.test.PullRequestTestHelper;
import com.atlassian.bitbucket.test.RepositoryTestHelper;
import com.atlassian.bitbucket.test.rest.RestAuthentication;
import com.atlassian.bitbucket.test.rest.commit.RestCommitHelper;
import com.google.common.collect.ImmutableMap;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.path.json.JsonPath;
import com.jayway.restassured.response.ResponseBody;
import net.sf.json.JSONObject;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import javax.ws.rs.core.MediaType;

import static com.atlassian.bitbucket.test.rest.ResponseMatchers.ok;
import static javax.ws.rs.core.Response.Status.CONFLICT;
import static javax.ws.rs.core.Response.Status.OK;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;

/**
 * Tests the {@code PullRequestResource#merge} method.
 * <p>
 * This is done separately from {@link PullRequestResourceTest} because it dirties the repository, requiring it to
 * be recreated for each test.
 */
public class PullRequestResourceMergeTest extends BasePullRequestResourceTest {

    private static final String COMMIT = "7b86ad1a05b4259b8fa54497a8be0bd359a405bd";
    private static final String COMMIT_MESSAGE = "Added a test file";
    private static final String MESSAGE = "Merged with an explicit strategyId: ";
    private static final String PR_TEST = "prmerge";
    private static final String AUTHOR = PR_TEST + "_author";
    private static final String URL = DefaultFuncTestData.getRepositoryRestURL(PR_TEST, PR_TEST) +
            "/settings/pull-requests";

    @BeforeClass
    public static void setupProjectAndUser() {
        getClassTestContext().user(AUTHOR)
                .project(PR_TEST)
                .projectPermission(PR_TEST, AUTHOR, Permission.PROJECT_WRITE);
    }

    @Before
    public void setupRepositoryAndPullRequest() throws Exception {
        testContext.repository(PR_TEST, PR_TEST, REPO_ZIP);

        pullRequest = createPullRequest(testContext, AUTHOR, PR_TEST);
    }

    @Test
    public void testMerge() throws Exception {
        runMessageTest("This text should appear in the commit message.");
    }

    @Test
    public void testMergeNoFF() throws Exception {
        runTest("no-ff");
    }

    @Test
    public void testMergeFF() throws Exception {
        runTest("ff");
    }

    @Test
    public void testMergeFFOnlyFailsWhenNotFastForward() throws Exception {
        verifyConflict("ff-only");
    }

    @Test
    public void testMergeFFOnly() throws Exception {
        closePullRequest();
        setupTest("pr_merge_test_ff_only");
        runTest(COMMIT_MESSAGE, "ff-only");
        pullRequest = null; //We don't close the PR twice
    }

    @Test
    public void testMergeSquash() throws Exception {
        runTest("squash");
    }

    @Test
    public void testMergeSquashFFOnly() throws Exception {
        closePullRequest();
        setupTest("pr_merge_test_squash_ff_only");
        runTest("squash-ff-only");
        pullRequest = null;
    }

    @Test
    public void testMergeSquashFFOnlyFailsWhenNotFastForward() throws Exception {
        verifyConflict("squash-ff-only");
    }

    @Ignore
    @Test
    public void testMergeWithUtf8Message() throws Exception {
        runMessageTest("測試 message");
    }

    private static void enableStrategy(String strategyId) throws Exception {
        ImmutableMap<String, Object> strategyMap = ImmutableMap.of("id", strategyId);
        MergeStrategiesTestHelper.setMergeStrategies(URL, DefaultFuncTestData.getAdminUser(),
                DefaultFuncTestData.getAdminPassword(), strategyMap, strategyMap);
    }

    private static JSONObject getMergeBody(String message, String strategyId) throws Exception {
        JSONObject body = new JSONObject();
        body.put("message", message);
        body.put("version", 0);

        if (strategyId != null) {
            body.put("strategyId", strategyId);
            enableStrategy(strategyId);
        }
        return body;
    }

    private void runMessageTest(String message) throws Exception {
        runTest(message, null);
    }

    private void runTest(String strategyId) throws Exception {
        runTest(MESSAGE + strategyId, strategyId);
    }

    private void runTest(String message, String strategyId) throws Exception {
        JSONObject body = getMergeBody(message, strategyId);

        ResponseBody responseBody = RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .log().ifStatusCodeMatches(not(ok()))
                .body("state", equalTo(PullRequestState.MERGED.name()))
                .body("version", equalTo(2)) //+1 for locking, +1 for MERGED+unlocking
                .given()
                .auth().preemptive().basic(AUTHOR, AUTHOR)
                .when()
                .body(body)
                .contentType(MediaType.APPLICATION_JSON)
                .post(pullRequest.getMergeUrl())
                .getBody();

        JsonPath commit = new RestCommitHelper(RestAuthentication.authenticated(AUTHOR), PR_TEST, PR_TEST, MASTER_BRANCH).get();
        assertThat(commit.getString("message"), containsString(message));
        assertEquals(commit.getString("id"), responseBody.jsonPath().getString("properties.mergeCommit.id"));
    }

    private void setupTest(String branch) throws Exception {
        testContext.branch(PR_TEST, PR_TEST, branch, COMMIT);
        RepositoryTestHelper.pushCommit(DefaultFuncTestData.getBaseURL(), DefaultFuncTestData.getAdminUser(),
                DefaultFuncTestData.getAdminPassword(), PR_TEST, PR_TEST, branch, AUTHOR, AUTHOR, "a", "a", "Contents",
                COMMIT_MESSAGE);
        pullRequest = testContext.pullRequest(new PullRequestTestHelper.Builder(AUTHOR, AUTHOR, "title", "description",
                PR_TEST, PR_TEST, branch,
                PR_TEST, PR_TEST, MASTER_BRANCH));
    }

    private void verifyConflict(String strategyId) throws Exception {
        enableStrategy(strategyId);
        JSONObject body = getMergeBody("", strategyId);

        RestAssured.expect()
                .statusCode(CONFLICT.getStatusCode())
                .given()
                .auth().preemptive().basic(AUTHOR, AUTHOR)
                .when()
                .body(body)
                .contentType(MediaType.APPLICATION_JSON)
                .post(pullRequest.getMergeUrl())
                .getBody();
    }
}
