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

import com.atlassian.bitbucket.content.DiffSegmentType;
import com.atlassian.bitbucket.test.DefaultFuncTestData;
import com.atlassian.bitbucket.test.PullRequestCommentLikeHelper;
import com.atlassian.bitbucket.test.PullRequestTestHelper;
import com.atlassian.bitbucket.test.TestContext;
import com.atlassian.bitbucket.test.rest.comment.RestCommentLikeUtils;
import com.atlassian.bitbucket.test.rest.comment.RestCommentUtils;
import com.atlassian.bitbucket.permission.Permission;
import com.google.common.collect.ImmutableList;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.path.json.JsonPath;
import com.jayway.restassured.response.ValidatableResponse;
import com.jayway.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.List;

import static javax.ws.rs.core.Response.Status.*;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

public class PullRequestCommentLikeResourceTest extends BasePullRequestResourceTest {

    private static final String PROJECT = DefaultFuncTestData.getProject1();
    private static final String REPO = DefaultFuncTestData.getProject1Repository1();

    private static final String LIKER = "liker";
    private static final String LIKER_BASE_NAME = "liker_";
    private static final String USER_AUTHOR = "author";
    private static final String USER_NON_REPO_READ = "non_repo_read";
    private static final int NUMBER_LIKERS = 5;

    private static final String PATH_TO_MODIFIED_FILE = "basic_branching/file.txt";

    private long commentId;
    private int commentVersion;
    private PullRequestCommentLikeHelper likeHelper;

    @BeforeClass
    public static void setupClass() throws Exception {
        TestContext context = getClassTestContext();

        context.user(USER_NON_REPO_READ)
                .user(USER_AUTHOR).projectPermission(PROJECT, USER_AUTHOR, Permission.PROJECT_READ)
                .user(LIKER).projectPermission(PROJECT, LIKER, Permission.PROJECT_READ);

        // create users for the paging limit tests
        for (int i = 0; i < NUMBER_LIKERS; i++) {
            String username = LIKER_BASE_NAME + i;
            context.user(username).projectPermission(PROJECT, username, Permission.PROJECT_READ);
        }
    }

    @Before
    public void setup() {
        pullRequest = testContext.pullRequest(new PullRequestTestHelper.Builder(
                USER_AUTHOR, USER_AUTHOR, PR_TITLE, PR_DESC,
                PROJECT, REPO, DefaultFuncTestData.getProject1Repository1BasicBranchingBranch(),
                PROJECT, REPO, DefaultFuncTestData.getProject1Repository1MasterBranch()));

        likeHelper = new PullRequestCommentLikeHelper(PROJECT, REPO, pullRequest.getId());

        JsonPath comment = pullRequest.addGeneralComment("Test Comment").jsonPath();
        commentId = comment.getLong("id");
        commentVersion = comment.getInt("version");
    }

    @Test
    public void testLike() {
        requestAs(LIKER)
                .when()
                    .post(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(NO_CONTENT.getStatusCode());

        requestAs(LIKER)
                .when()
                    .get(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(OK.getStatusCode())
                    .body("size", equalTo(1))
                    .body("values[0].slug", equalTo(LIKER));
    }

    @Test
    public void testUnlike() {
        likeHelper.likeComment(commentId, LIKER);

        requestAs(LIKER)
                .when()
                    .delete(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(NO_CONTENT.getStatusCode());

        requestAs(LIKER)
                .when()
                    .get(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(OK.getStatusCode())
                    .body("size", equalTo(0));
    }

    @Test
    public void testLikeAsAuthor() {
        requestAs(USER_AUTHOR)
                .when()
                    .post(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(BAD_REQUEST.getStatusCode());

        requestAs(LIKER)
                .when()
                    .get(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(OK.getStatusCode())
                    .body("size", equalTo(0));
    }

    @Test
    public void testLikeAsAnonymous() {
        requestAsAnon()
                .when()
                    .post(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(UNAUTHORIZED.getStatusCode());
    }

    @Test
    public void testGetLikesAsAnonymous() {
        requestAsAnon()
                .when()
                    .get(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(UNAUTHORIZED.getStatusCode());
    }

    @Test
    public void testLikeNonexistentComment() {
        requestAs(LIKER)
                .when()
                    .get(getLikesEndpoint(Long.MAX_VALUE))
                .then()
                    .log().ifValidationFails()
                    .statusCode(NOT_FOUND.getStatusCode());
    }

    @Test
    public void testLikePaging() {
        likeCommentByAll();

        // there's 5 likes, requesting pages of 3, which should result in 2 pages of [3], [2] likes
        int PAGE_LIMIT = 3;
        int nextPageStart = requestAs(LIKER)
                .when()
                    .with().queryParam("start", 0).queryParam("limit", PAGE_LIMIT)
                    .get(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(OK.getStatusCode())
                    .body("size", equalTo(PAGE_LIMIT))
                    .body("limit", equalTo(PAGE_LIMIT))
                    .body("values.size()", equalTo(PAGE_LIMIT))
                    .body("isLastPage", is(false))
                .extract()
                    .path("nextPageStart");

        requestAs(LIKER)
                .when()
                .with().queryParam("start", nextPageStart).queryParam("limit", PAGE_LIMIT)
                .get(getLikesEndpoint())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body("size", equalTo(2))
                .body("limit", equalTo(PAGE_LIMIT))
                .body("values.size()", equalTo(2))
                .body("isLastPage", is(true));
    }

    @Test
    public void testRequestingUserIsFirst() {
        likeCommentByAll();

        // get list of likers as each of the users
        // expecting the user to be the first person returned
        for (int testUserIndex = 0; testUserIndex < NUMBER_LIKERS; testUserIndex++) {
            String username = LIKER_BASE_NAME + testUserIndex;
            ValidatableResponse response = requestAs(username)
                    .when()
                        .get(getLikesEndpoint())
                    .then()
                        .log().ifValidationFails()
                        .statusCode(OK.getStatusCode())
                        .body("values[0].slug", equalTo(username));

            // ensure that users are returned in the order they liked (except for the fist one)
            // valuesIndex starts at 1 because 0 has already been checked in the restAssured response
            for (int usernameIndex = 0, valuesIndex = 1; usernameIndex < NUMBER_LIKERS; usernameIndex++) {
                // skip as testUserIndex was checked to be values[0]
                if (usernameIndex == testUserIndex) {
                    continue;
                }
                response.body("values[" + valuesIndex + "].slug", equalTo(LIKER_BASE_NAME + usernameIndex));
                valuesIndex++;
            }
        }
    }

    @Test
    public void testPullRequestDiffHasLikedByProperty() {
        long diffCommentId = pullRequest
                .addDiffComment("Test Comment", PATH_TO_MODIFIED_FILE, 1, DiffSegmentType.ADDED)
                .jsonPath().getLong("id");
        likeHelper.likeComment(diffCommentId, LIKER);

        requestAs(LIKER)
                .when()
                    .get(pullRequest.getDiffUrl(null, null))
                .then()
                    .log().ifValidationFails()
                    .specification(RestCommentLikeUtils.expectLikedBy("diffs[0].lineComments[0]", ImmutableList.of(LIKER)))
                    .statusCode(OK.getStatusCode());
    }

    @Test
    public void testPullRequestDiffReplyHasLikedByProperty() {
        long diffCommentId = pullRequest
                .addDiffComment("Test Comment", PATH_TO_MODIFIED_FILE, 1, DiffSegmentType.ADDED)
                .jsonPath().getLong("id");
        long commentReplyId = pullRequest
                .addCommentReply("Test reply", diffCommentId)
                .jsonPath().getLong("id");
        likeHelper.likeComment(commentReplyId, LIKER);

        requestAs(LIKER)
                .when()
                    .get(pullRequest.getDiffUrl(null, null))
                .then()
                    .log().ifValidationFails()
                    .specification(RestCommentLikeUtils.expectLikedBy("diffs[0].lineComments[0].comments[0]", ImmutableList.of(LIKER)))
                    .statusCode(OK.getStatusCode());
    }

    @Test
    public void testLikedByProperty() {
        List<String> users = likeCommentByAll();

        requestAs(LIKER)
                .when()
                .get(pullRequest.getCommentUrl(commentId, 0))
                .then()
                .log().ifValidationFails()
                .specification(RestCommentLikeUtils.expectLikedBy("", users, NUMBER_LIKERS));
    }

    @Test
    public void testPullRequestActivityGeneralCommentHasLikedByProperty() {
        likeHelper.likeComment(commentId, LIKER);

        requestAs(LIKER)
                .when()
                    .get(pullRequest.getActivitiesUrl())
                .then()
                    .log().ifValidationFails()
                    .specification(RestCommentLikeUtils.expectLikedBy("values[0].comment", ImmutableList.of(LIKER)))
                    .statusCode(OK.getStatusCode());
    }

    @Test
    public void testPullRequestActivityLineCommentHasLikedByProperty() {
        long diffCommentId = pullRequest
                .addDiffComment("Test Comment", PATH_TO_MODIFIED_FILE, 1, DiffSegmentType.ADDED)
                .jsonPath().getLong("id");
        likeHelper.likeComment(diffCommentId, LIKER);

        requestAs(LIKER)
                .when()
                    .get(pullRequest.getActivitiesUrl())
                .then()
                    .log().ifValidationFails()
                    .specification(RestCommentLikeUtils.expectLikedBy("values[0].comment", ImmutableList.of(LIKER)))
                    .statusCode(OK.getStatusCode());
    }

    @Test
    public void pullRequestActivityReplyCommentHasLikedByProperty() {
        long diffCommentId = pullRequest
                .addDiffComment("Test Comment", PATH_TO_MODIFIED_FILE, 1, DiffSegmentType.ADDED)
                .jsonPath().getLong("id");
        long commentReplyId = pullRequest
                .addCommentReply("Test reply", diffCommentId)
                .jsonPath().getLong("id");
        likeHelper.likeComment(commentReplyId, LIKER);

        requestAs(LIKER)
                .when()
                    .get(pullRequest.getActivitiesUrl())
                .then()
                    .log().ifValidationFails()
                    .specification(RestCommentLikeUtils.expectLikedBy("values[0].comment.comments[0]", ImmutableList.of(LIKER)))
                    .statusCode(OK.getStatusCode());
    }

    @Test
    public void testPullRequestCommentEditHasLikedByProperty() {
        likeHelper.likeComment(commentId, LIKER);

        requestAs(USER_AUTHOR)
                .when()
                    .body(RestCommentUtils.createBodyWithVersion("A revised comment", commentVersion))
                    .put(pullRequest.getCommentUrl(commentId, commentVersion))
                .then()
                    .log().ifValidationFails()
                    .specification(RestCommentLikeUtils.expectLikedBy("", ImmutableList.of(LIKER)));
    }

    @Test
    public void testLikingLikedComment() {
        likeHelper.likeComment(commentId, LIKER);

        requestAs(LIKER)
                .when()
                    .post(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(NO_CONTENT.getStatusCode());
    }

    @Test
    public void testUnlikingUnlikedComment() {
        requestAs(LIKER)
                .when()
                    .delete(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(NO_CONTENT.getStatusCode());
    }

    @Test
    public void testLikeAsUserWithNoRepoRead() {
        requestAs(USER_NON_REPO_READ)
                .when()
                    .post(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(UNAUTHORIZED.getStatusCode());
    }

    @Test
    public void testGetLikersAsUserWithNoRepoRead() {
        likeHelper.likeComment(commentId, LIKER);

        requestAs(USER_NON_REPO_READ)
                .when()
                    .get(getLikesEndpoint())
                .then()
                    .log().ifValidationFails()
                    .statusCode(UNAUTHORIZED.getStatusCode());
    }

    private static RequestSpecification requestAs(String username) {
        return requestAs(username, username);
    }

    private static RequestSpecification requestAs(String username, String password) {
        return RestAssured.given().auth().preemptive().basic(username, password).contentType(ContentType.JSON);
    }

    private static RequestSpecification requestAsAnon() {
        return RestAssured.given().contentType(ContentType.JSON);
    }

    private String getLikesEndpoint() {
        return getLikesEndpoint(commentId);
    }

    private String getLikesEndpoint(long commentId) {
        return likeHelper.getCommentLikesURL(commentId);
    }

    private List<String> likeCommentByAll() {
        ImmutableList.Builder<String> usernames = ImmutableList.builder();
        for (int i = 0; i < NUMBER_LIKERS; i++) {
            likeHelper.likeComment(commentId, LIKER_BASE_NAME + i);
            usernames.add(LIKER_BASE_NAME + i);
        }

        return usernames.build();
    }
}
