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

import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.pull.PullRequestParticipantStatus;
import com.atlassian.bitbucket.pull.PullRequestRole;
import com.atlassian.bitbucket.rest.pull.RestPullRequestParticipant;
import com.atlassian.bitbucket.test.PullRequestTestHelper;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.specification.ResponseSpecification;
import net.sf.json.JSONObject;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.ws.rs.core.MediaType;

import static javax.servlet.http.HttpServletResponse.*;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

public class PullRequestParticipantResourceTest extends BasePullRequestParticipantResourceTest {

    private static final String PR_PARTICIPANT_TEST = "prparticipanttest";
    private static final String PROJ_WRITER = PR_PARTICIPANT_TEST + "_writer";
    private static final String AUTHOR = PR_PARTICIPANT_TEST + "_author";
    private static final String PARTICIPANT = "participant";
    private static final String REVIEWER = PR_PARTICIPANT_TEST + "_reviewer";
    private static final String REVIEWER2 = PR_PARTICIPANT_TEST + "_reviewer2";

    @BeforeClass
    public static void setup() throws Exception {
        getClassTestContext()
                .project(PR_PARTICIPANT_TEST)
                .repository(PR_PARTICIPANT_TEST, PR_PARTICIPANT_TEST, REPO_ZIP)
                .user(PROJ_WRITER).projectPermission(PR_PARTICIPANT_TEST, PROJ_WRITER, Permission.PROJECT_WRITE)
                .user(AUTHOR).projectPermission(PR_PARTICIPANT_TEST, AUTHOR, Permission.PROJECT_READ)
                .user(REVIEWER).projectPermission(PR_PARTICIPANT_TEST, REVIEWER, Permission.PROJECT_READ)
                .user(REVIEWER2).projectPermission(PR_PARTICIPANT_TEST, REVIEWER2, Permission.PROJECT_READ)
                .user(PARTICIPANT).projectPermission(PR_PARTICIPANT_TEST, PARTICIPANT, Permission.PROJECT_READ);
    }

    @Before
    public void setupPullRequest() {
        pullRequest = createPullRequest(testContext, AUTHOR, PR_PARTICIPANT_TEST);
    }

    @Test
    public void testAddReviewer() {
        expect(SC_OK)
                .body(RestPullRequestParticipant.USER, notNullValue())
                .body(RestPullRequestParticipant.USER + ".name", equalTo(REVIEWER))
                .body(RestPullRequestParticipant.LAST_REVIEWED_COMMIT, nullValue())
                .body(RestPullRequestParticipant.ROLE, equalTo(PullRequestRole.REVIEWER.name()))
                .given()
                .auth().preemptive().basic(PROJ_WRITER, PROJ_WRITER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createParticipantUpdateBody(REVIEWER, PullRequestRole.REVIEWER))
                .post(pullRequest.getParticipantsUrl());
    }

    @Test
    public void testCannotAddAuthor() {
        expect(SC_BAD_REQUEST)
                .given()
                .auth().preemptive().basic(PROJ_WRITER, PROJ_WRITER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createParticipantUpdateBody(AUTHOR, PullRequestRole.AUTHOR))
                .post(pullRequest.getParticipantsUrl());
    }

    @Test
    public void testCannotAddGeneralParticipant() {
        expect(SC_BAD_REQUEST)
                .given()
                .auth().preemptive().basic(PROJ_WRITER, PROJ_WRITER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createParticipantUpdateBody(REVIEWER, PullRequestRole.PARTICIPANT))
                .post(pullRequest.getParticipantsUrl());
    }

    @Test
    public void testCannotDeleteAuthor() {
        expect(SC_BAD_REQUEST)
                .given()
                .auth().preemptive().basic(PROJ_WRITER, PROJ_WRITER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createParticipantUpdateBody(AUTHOR, PullRequestRole.AUTHOR))
                .post(pullRequest.getParticipantsUrl());
    }

    @Test
    public void testDeleteReviewer() {
        expect(SC_OK)
                .body(RestPullRequestParticipant.USER, notNullValue())
                .body(RestPullRequestParticipant.USER + ".name", equalTo(REVIEWER))
                .body(RestPullRequestParticipant.LAST_REVIEWED_COMMIT, nullValue())
                .body(RestPullRequestParticipant.ROLE, equalTo(PullRequestRole.REVIEWER.name()))
                .given()
                .auth().preemptive().basic(PROJ_WRITER, PROJ_WRITER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createParticipantUpdateBody(REVIEWER, PullRequestRole.REVIEWER))
                .post(pullRequest.getParticipantsUrl());

        expect(SC_NO_CONTENT)
                .given()
                .auth().preemptive().basic(PROJ_WRITER, PROJ_WRITER)
                .contentType(MediaType.APPLICATION_JSON)
                .delete(pullRequest.getDeleteParticipantsUrl(REVIEWER));
    }

    @Test
    public void testDeleteReviewerWithSlugInUrl() {
        expect(SC_OK)
                .body(RestPullRequestParticipant.USER, notNullValue())
                .body(RestPullRequestParticipant.USER + ".name", equalTo(REVIEWER))
                .body(RestPullRequestParticipant.LAST_REVIEWED_COMMIT, nullValue())
                .body(RestPullRequestParticipant.ROLE, equalTo(PullRequestRole.REVIEWER.name()))
                .given()
                .auth().preemptive().basic(PROJ_WRITER, PROJ_WRITER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createParticipantUpdateBody(REVIEWER, PullRequestRole.REVIEWER))
                .post(pullRequest.getParticipantsUrl());

        expect(SC_NO_CONTENT)
                .given()
                .auth().preemptive().basic(PROJ_WRITER, PROJ_WRITER)
                .contentType(MediaType.APPLICATION_JSON)
                .delete(pullRequest.getParticipantsUrl() + "/" + REVIEWER);
    }

    @Test
    public void testUpdateStatusForAuthor() {
        expect(SC_BAD_REQUEST)
                .given()
                .auth().preemptive().basic(AUTHOR, AUTHOR)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createStatusUpdateBody(PullRequestParticipantStatus.APPROVED))
                .put(pullRequest.getUpdatePullRequestStatusUrl(AUTHOR));
    }

    @Test
    public void testUpdateStatusToApproved() {
        pullRequest.addReviewers(REVIEWER);

        expect(SC_OK)
                .body(RestPullRequestParticipant.USER, notNullValue())
                .body(RestPullRequestParticipant.USER + ".name", equalTo(REVIEWER))
                .body(RestPullRequestParticipant.LAST_REVIEWED_COMMIT, equalTo("7549846524f8aed2bd1c0249993ae1bf9d3c9998"))
                .body(RestPullRequestParticipant.ROLE, equalTo(PullRequestRole.REVIEWER.name()))
                .body(RestPullRequestParticipant.APPROVED, equalTo(true))
                .body(RestPullRequestParticipant.STATUS, equalTo(PullRequestParticipantStatus.APPROVED.name()))
                .given()
                .auth().preemptive().basic(REVIEWER, REVIEWER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createStatusUpdateBody(PullRequestParticipantStatus.APPROVED))
                .put(pullRequest.getUpdatePullRequestStatusUrl(REVIEWER));
    }

    @Test
    public void testUpdateStatusToApprovedForNonReviewer() {
        expect(SC_OK)
                .body(RestPullRequestParticipant.USER, notNullValue())
                .body(RestPullRequestParticipant.USER + ".name", equalTo(PARTICIPANT))
                .body(RestPullRequestParticipant.LAST_REVIEWED_COMMIT, equalTo("7549846524f8aed2bd1c0249993ae1bf9d3c9998"))
                .body(RestPullRequestParticipant.ROLE, equalTo(PullRequestRole.REVIEWER.name()))
                .body(RestPullRequestParticipant.APPROVED, equalTo(true))
                .body(RestPullRequestParticipant.STATUS, equalTo(PullRequestParticipantStatus.APPROVED.name()))
                .given()
                .auth().preemptive().basic(PARTICIPANT, PARTICIPANT)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createStatusUpdateBody(PullRequestParticipantStatus.APPROVED))
                .put(pullRequest.getUpdatePullRequestStatusUrl(PARTICIPANT));
    }

    @Test
    public void testUpdateStatusToNeedsWork() {
        pullRequest.addReviewers(REVIEWER);

        expect(SC_OK)
                .body(RestPullRequestParticipant.USER, notNullValue())
                .body(RestPullRequestParticipant.USER + ".name", equalTo(REVIEWER))
                .body(RestPullRequestParticipant.LAST_REVIEWED_COMMIT, equalTo("7549846524f8aed2bd1c0249993ae1bf9d3c9998"))
                .body(RestPullRequestParticipant.ROLE, equalTo(PullRequestRole.REVIEWER.name()))
                .body(RestPullRequestParticipant.APPROVED, equalTo(false))
                .body(RestPullRequestParticipant.STATUS, equalTo(PullRequestParticipantStatus.NEEDS_WORK.name()))
                .given()
                .auth().preemptive().basic(REVIEWER, REVIEWER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createStatusUpdateBody(PullRequestParticipantStatus.NEEDS_WORK))
                .put(pullRequest.getUpdatePullRequestStatusUrl(REVIEWER));
    }

    @Test
    public void testUpdateStatusToNeedsWorkForNonReviewer() {
        expect(SC_OK)
                .body(RestPullRequestParticipant.USER, notNullValue())
                .body(RestPullRequestParticipant.USER + ".name", equalTo(PARTICIPANT))
                .body(RestPullRequestParticipant.LAST_REVIEWED_COMMIT, equalTo("7549846524f8aed2bd1c0249993ae1bf9d3c9998"))
                .body(RestPullRequestParticipant.ROLE, equalTo(PullRequestRole.REVIEWER.name()))
                .body(RestPullRequestParticipant.APPROVED, equalTo(false))
                .body(RestPullRequestParticipant.STATUS, equalTo(PullRequestParticipantStatus.NEEDS_WORK.name()))
                .given()
                .auth().preemptive().basic(PARTICIPANT, PARTICIPANT)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createStatusUpdateBody(PullRequestParticipantStatus.NEEDS_WORK))
                .put(pullRequest.getUpdatePullRequestStatusUrl(PARTICIPANT));
    }

    @Test
    public void testUpdateStatusToUnapproved() {
        pullRequest.addReviewers(REVIEWER);

        expect(SC_OK)
                .body(RestPullRequestParticipant.USER, notNullValue())
                .body(RestPullRequestParticipant.USER + ".name", equalTo(REVIEWER))
                .body(RestPullRequestParticipant.LAST_REVIEWED_COMMIT, nullValue())
                .body(RestPullRequestParticipant.ROLE, equalTo(PullRequestRole.REVIEWER.name()))
                .body(RestPullRequestParticipant.APPROVED, equalTo(false))
                .body(RestPullRequestParticipant.STATUS, equalTo(PullRequestParticipantStatus.UNAPPROVED.name()))
                .given()
                .auth().preemptive().basic(REVIEWER, REVIEWER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createStatusUpdateBody(PullRequestParticipantStatus.UNAPPROVED))
                .put(pullRequest.getUpdatePullRequestStatusUrl(REVIEWER));
    }

    @Test
    public void testUpdateStatusToUnapprovedForNonReviewer() {
        expect(SC_NOT_FOUND)
                .given()
                .auth().preemptive().basic(PARTICIPANT, PARTICIPANT)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createStatusUpdateBody(PullRequestParticipantStatus.UNAPPROVED))
                .put(pullRequest.getUpdatePullRequestStatusUrl(PARTICIPANT));
    }

    @Test
    public void testUpdateStatusToUnapprovedDoesNotUpdateLastReviewedCommit() {
        pullRequest.addReviewers(REVIEWER);
        pullRequest.setStatusFor(REVIEWER, PullRequestParticipantStatus.APPROVED);

        expect(SC_OK)
                .body(RestPullRequestParticipant.USER, notNullValue())
                .body(RestPullRequestParticipant.USER + ".name", equalTo(REVIEWER))
                .body(RestPullRequestParticipant.LAST_REVIEWED_COMMIT, equalTo("7549846524f8aed2bd1c0249993ae1bf9d3c9998"))
                .body(RestPullRequestParticipant.ROLE, equalTo(PullRequestRole.REVIEWER.name()))
                .body(RestPullRequestParticipant.APPROVED, equalTo(false))
                .body(RestPullRequestParticipant.STATUS, equalTo(PullRequestParticipantStatus.UNAPPROVED.name()))
                .given()
                .auth().preemptive().basic(REVIEWER, REVIEWER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createStatusUpdateBody(PullRequestParticipantStatus.UNAPPROVED))
                .put(pullRequest.getUpdatePullRequestStatusUrl(REVIEWER));
    }

    @Test
    public void testUpdateStatusWithWrongUser() {
        expect(SC_FORBIDDEN)
                .given()
                .auth().preemptive().basic(REVIEWER, REVIEWER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createStatusUpdateBody(PullRequestParticipantStatus.APPROVED))
                .put(pullRequest.getUpdatePullRequestStatusUrl("WrongUser"));
    }

    @Test
    public void testUpdateStatusWithUnauthenticatedUser() {
        expect(SC_UNAUTHORIZED)
                .given()
                .contentType(MediaType.APPLICATION_JSON)
                .body(PullRequestTestHelper.toJsonObject(RestPullRequestParticipant.STATUS, PullRequestParticipantStatus.APPROVED))
                .put(pullRequest.getUpdatePullRequestStatusUrl(REVIEWER));
    }

    @Test
    public void testUpdateStatusWithInvalidValue() {
        JSONObject badStatus = new JSONObject();
        badStatus.put(RestPullRequestParticipant.STATUS, "BadStatus");
        expect(SC_BAD_REQUEST)
                .given()
                .auth().preemptive().basic(REVIEWER, REVIEWER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(badStatus)
                .put(pullRequest.getUpdatePullRequestStatusUrl(REVIEWER));
    }

    @Test
    public void testUpdateStatusWithPost() {
        expect(SC_METHOD_NOT_ALLOWED)
                .given()
                .auth().preemptive().basic(REVIEWER, REVIEWER)
                .contentType(MediaType.APPLICATION_JSON)
                .body(createStatusUpdateBody(PullRequestParticipantStatus.UNAPPROVED))
                .post(pullRequest.getUpdatePullRequestStatusUrl(REVIEWER));
    }

    public static JSONObject createParticipantUpdateBody(String username, PullRequestRole role) {
        JSONObject participantObject = new JSONObject();
        JSONObject userObject = new JSONObject();
        userObject.put("name", username);
        participantObject.put(RestPullRequestParticipant.USER, userObject);
        participantObject.put(RestPullRequestParticipant.ROLE, role.name());
        return participantObject;
    }

    private static JSONObject createStatusUpdateBody(PullRequestParticipantStatus status) {
        return PullRequestTestHelper.toJsonObject(RestPullRequestParticipant.STATUS, status);
    }

    private static ResponseSpecification expect(int statusCode) {
        return RestAssured.expect().log().ifError().statusCode(statusCode);
    }
}
