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

import com.atlassian.bitbucket.pull.PullRequestParticipantStatus;
import com.atlassian.bitbucket.pull.PullRequestRole;
import com.atlassian.bitbucket.test.DefaultFuncTestData;
import com.atlassian.bitbucket.test.PullRequestTestHelper;
import com.atlassian.bitbucket.permission.Permission;
import com.jayway.restassured.RestAssured;
import it.com.atlassian.stash.BaseRestTest;
import org.junit.*;
import org.springframework.core.io.ClassPathResource;

import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.OK;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;

public class InboxResourceTest extends BaseRestTest {

    private static final String BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_SRC = "branch_that_has_file_permissions_changed_src";
    private static final String BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_TRGT = "branch_that_has_file_permissions_changed_trgt";
    private static final String BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_SRC = "branch_that_has_same_file_modified_and_moved_src";
    private static final String BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_TRGT = "branch_that_has_same_file_modified_and_moved_trgt";
    private static final String BRANCH_THAT_HAS_SAME_TEXT_FILE_MODIFIED_SRC = "branch_that_has_same_text_file_modified_src";
    private static final String BRANCH_THAT_HAS_SAME_TEXT_FILE_MODIFIED_TRGT = "branch_that_has_same_text_file_modified_trgt";
    private static final String BRANCH_WITH_AUTOMERGE_CONFLICT_SRC = "automerge_conflict/1.0";
    private static final String BRANCH_WITH_AUTOMERGE_CONFLICT_TRGT = "automerge_conflict/2.0";
    private static final String GIT_PR_RESOURCES_ZIP = "git/pull-requests.zip";
    private static final String PR_AUTHOR = "pr_author";
    private static final String PR_AUTHOR_TWO = "pr_author_2";
    private static final String PR_DESCRIPTION = "Pull request description test.";
    private static final String PR_REVIEWER = "pr_reviewer";
    private static final String PR_TITLE = "Pull Request Title";
    private static final String PROJECT_KEY = "PR_TEST";
    private static final String REPO_SLUG = "inbox_repo";

    private PullRequestTestHelper pullRequest;

    @BeforeClass
    public static void setup() throws Exception {
        getClassTestContext()
                .project(PROJECT_KEY)
                .repository(PROJECT_KEY, REPO_SLUG, new ClassPathResource(GIT_PR_RESOURCES_ZIP))
                .user(PR_AUTHOR)
                .user(PR_AUTHOR_TWO)
                .user(PR_REVIEWER)
                .repositoryPermission(PROJECT_KEY, REPO_SLUG, PR_AUTHOR, Permission.REPO_WRITE)
                .repositoryPermission(PROJECT_KEY, REPO_SLUG, PR_AUTHOR_TWO, Permission.REPO_WRITE)
                .repositoryPermission(PROJECT_KEY, REPO_SLUG, PR_REVIEWER, Permission.REPO_READ);
    }

    @Before
    public void setupPullRequest() {
        pullRequest = createPullRequest(PR_AUTHOR, BRANCH_THAT_HAS_SAME_TEXT_FILE_MODIFIED_SRC,
                BRANCH_THAT_HAS_SAME_TEXT_FILE_MODIFIED_TRGT);
        pullRequest.addReviewers(PR_REVIEWER);
    }

    @Test
    public void testInboxCountWithApprovedPR() {
        // PRs are only added to the count if status is UNAPPROVED
        pullRequest.setStatusFor(PR_REVIEWER, PullRequestParticipantStatus.APPROVED);
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("count", equalTo(0))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getInboxCountUrl());
    }

    @Test
    public void testInboxCountWithNeedsWorkPR() {
        // PRs are only added to the count if status is UNAPPROVED
        pullRequest.setStatusFor(PR_REVIEWER, PullRequestParticipantStatus.NEEDS_WORK);
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("count", equalTo(0))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getInboxCountUrl());
    }

    @Test
    public void testInboxCountWithNoPR() {
        // PRs are only added to the count when user is a reviewer, not an author
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("count", equalTo(0))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_AUTHOR, PR_AUTHOR)
                .when()
                .get(getInboxCountUrl());
    }

    @Test
    public void testInboxCountWithUnapprovedPR() {
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("count", equalTo(1))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getInboxCountUrl());
    }

    @Test
    public void testInboxPullRequestsAsAuthor() {
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(1))
                .body("values[0].id", equalTo((int) pullRequest.getId()))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_AUTHOR, PR_AUTHOR)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.AUTHOR));
    }

    @Test
    public void testInboxPullRequestsAsAuthorAndReviewer() {
        PullRequestTestHelper secondPullRequest = createPullRequest(PR_AUTHOR_TWO,
                BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_SRC, BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_TRGT);
        secondPullRequest.addReviewers(PR_AUTHOR);

        // PR_AUTHOR is the author of one PR and a reviewer on the other
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(1))
                .body("values[0].id", equalTo((int) pullRequest.getId()))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_AUTHOR, PR_AUTHOR)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.AUTHOR));

        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(1))
                .body("values[0].id", equalTo((int) secondPullRequest.getId()))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_AUTHOR, PR_AUTHOR)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.REVIEWER));
    }

    @Test
    public void testInboxPullRequestsAsAuthorWithNoPullRequests() {
        // The PR_REVIEWER user is not the author of any PRs
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(0))
                .body("values", empty())
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.AUTHOR));
    }

    @Test
    public void testInboxPullRequestsAsAuthorWithPullRequestClosed() {
        // Only open pull requests are included in inbox
        pullRequest.declineSafe();
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(0))
                .body("values", empty())
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_AUTHOR, PR_AUTHOR)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.AUTHOR));
    }

    @Test
    public void testInboxPullRequestsAsReviewerWithApprovedPR() {
        // PRs are not included in the inbox if user has APPROVED
        pullRequest.setStatusFor(PR_REVIEWER, PullRequestParticipantStatus.APPROVED);
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(0))
                .body("values", empty())
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.REVIEWER));
    }

    @Test
    public void testInboxPullRequestsAsReviewerWithNeedsWorkPR() {
        pullRequest.setStatusFor(PR_REVIEWER, PullRequestParticipantStatus.NEEDS_WORK);
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(1))
                .body("values[0].id", equalTo((int) pullRequest.getId()))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.REVIEWER));
    }

    @Test
    public void testInboxPullRequestsAsReviewerWithNoPullRequests() {
        // The PR_AUTHOR user is not the reviewer of any PRs
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(0))
                .body("values", empty())
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_AUTHOR, PR_AUTHOR)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.REVIEWER));
    }

    @Test
    public void testInboxPullRequestsAsReviewerWithUnapprovedPR() {
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(1))
                .body("values[0].id", equalTo((int) pullRequest.getId()))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.REVIEWER));
    }

    @Test
    public void testInboxPullRequestsInvalidRole() {
        RestAssured.expect()
                .statusCode(BAD_REQUEST.getStatusCode())
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getBaseInboxUrl() + "?role=admin");
    }

    @Test
    public void testInboxPullRequestsOrderByStatus() {
        PullRequestTestHelper needsWorkPullRequest = createPullRequest(PR_AUTHOR,
                BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_SRC, BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_TRGT);
        needsWorkPullRequest.addReviewers(PR_REVIEWER);
        needsWorkPullRequest.setStatusFor(PR_REVIEWER, PullRequestParticipantStatus.NEEDS_WORK);

        // UNAPPROVED, then NEEDS_WORKS
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(2))
                .body("values[0].id", equalTo((int) pullRequest.getId()))
                .body("values[1].id", equalTo((int) needsWorkPullRequest.getId()))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.REVIEWER));
    }

    @Test
    public void testInboxPullRequestsOrderByTimestamp() {
        PullRequestTestHelper newerPullRequest = createPullRequest(PR_AUTHOR,
                BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_SRC, BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_TRGT);
        newerPullRequest.addReviewers(PR_REVIEWER);

        // Newest first
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(2))
                .body("values[0].id", equalTo((int) newerPullRequest.getId()))
                .body("values[1].id", equalTo((int) pullRequest.getId()))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.REVIEWER));
    }

    @Test
    public void testInboxPullRequestsOrderByTimestampAndStatus() {
        PullRequestTestHelper secondPullRequest = createPullRequest(PR_AUTHOR,
                BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_SRC, BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_TRGT);
        secondPullRequest.addReviewers(PR_REVIEWER);

        PullRequestTestHelper thirdPullRequest = createPullRequest(PR_AUTHOR,
                BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_SRC, BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_TRGT);
        thirdPullRequest.addReviewers(PR_REVIEWER);

        PullRequestTestHelper fourthPullRequest = createPullRequest(PR_AUTHOR,
                BRANCH_WITH_AUTOMERGE_CONFLICT_SRC, BRANCH_WITH_AUTOMERGE_CONFLICT_TRGT);
        fourthPullRequest.addReviewers(PR_REVIEWER);

        // PR1 - NEEDS_WORK
        // PR2 - UNAPPROVED
        // PR3 - NEEDS_WORK
        // PR4 - UNAPPROVED
        // update PR1 (becomes newer)
        pullRequest.setStatusFor(PR_REVIEWER, PullRequestParticipantStatus.NEEDS_WORK);
        thirdPullRequest.setStatusFor(PR_REVIEWER, PullRequestParticipantStatus.NEEDS_WORK);
        pullRequest.setDescription("Updated pull request");

        // Order should be: [PR4, PR2, PR1, PR3]
        RestAssured.expect()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(4))
                .body("values[0].id", equalTo((int) fourthPullRequest.getId()))
                .body("values[1].id", equalTo((int) secondPullRequest.getId()))
                .body("values[2].id", equalTo((int) pullRequest.getId()))
                .body("values[3].id", equalTo((int) thirdPullRequest.getId()))
                .log().ifError()
                .given()
                .auth().preemptive().basic(PR_REVIEWER, PR_REVIEWER)
                .when()
                .get(getInboxUrlForRole(PullRequestRole.REVIEWER));
    }

    private PullRequestTestHelper createPullRequest(String author, String sourceBranch, String targetBranch) {
        return testContext.pullRequest(new PullRequestTestHelper.Builder(author, author, PR_TITLE,
                PR_DESCRIPTION, PROJECT_KEY, REPO_SLUG, sourceBranch, PROJECT_KEY, REPO_SLUG, targetBranch));
    }

    private String getBaseInboxUrl() {
        return DefaultFuncTestData.getRestURL() + "/inbox/pull-requests";
    }

    private String getInboxCountUrl() {
        return getBaseInboxUrl() + "/count";
    }

    private String getInboxUrlForRole(PullRequestRole role) {
        return getBaseInboxUrl() + "?role=" + role.name();
    }
}
