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

import com.atlassian.bitbucket.task.TaskAnchorType;
import com.atlassian.bitbucket.task.TaskState;
import com.atlassian.bitbucket.test.PullRequestTestHelper;
import com.atlassian.bitbucket.test.TaskTestHelper;
import com.google.common.collect.ImmutableMap;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.http.ContentType;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.Date;

import static com.atlassian.bitbucket.hamcrest.MoreMatchers.longEqual;
import static javax.ws.rs.core.Response.Status.*;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.hamcrest.object.HasToString.hasToString;

public class TaskUpdateResourceTest extends AbstractTaskResourceTest {

    public static PullRequestTestHelper pullRequest;

    @BeforeClass
    public static void setupRepository() throws Exception {
        defaultSetupRepository(getClassTestContext());
        pullRequest = getClassTestContext().pullRequest(
                new PullRequestTestHelper.Builder(
                        USER_PR_AUTHOR, USER_PR_AUTHOR,
                        PR_TITLE, PR_TITLE,
                        PROJECT_NAME, PROJECT_NAME, SOURCE_BRANCH,
                        PROJECT_NAME, PROJECT_NAME, TARGET_BRANCH));
    }

    @Test
    public void testTaskCreatorCanUpdateBothStateAndText() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        String newText = "new comment text";
        String newState = TaskState.RESOLVED.name();

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.builder()
                        .put(ID, task.getId())
                        .put(AUTHOR, task.getAuthor())
                        .put(CREATED_DATE, task.getCreatedDate())
                        .put(STATE, newState)
                        .put(ANCHOR, ImmutableMap.of(ID, task.getCommentId(),
                                ANCHOR_TYPE, TaskAnchorType.COMMENT))
                        .put(TEXT, newText)
                        .build())
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body(ID, longEqual(task.getId()))
                .body(STATE, equalTo(newState))
                .body(TEXT, equalTo(newText))
                .body(ANCHOR_ID, longEqual(task.getCommentId()))
                .body(ANCHOR_TYPE, equalTo(TaskAnchorType.COMMENT.name()))
                .body(CREATED_BY, equalTo(task.getAuthor()))
                .body(CREATED_DATE, hasToString(task.getCreatedDate()));
    }

    @Test
    public void testPullRequestAuthorCanChangeTaskText() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        String newText = "new comment text";

        RestAssured
                .given()
                .auth().preemptive().basic(USER_PR_AUTHOR, USER_PR_AUTHOR)
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, newText))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body(ID, longEqual(task.getId()))
                .body(STATE, equalTo(task.getState()))
                .body(TEXT, equalTo(newText))
                .body(ANCHOR_ID, longEqual(task.getCommentId()))
                .body(ANCHOR_TYPE, equalTo(TaskAnchorType.COMMENT.name()))
                .body(CREATED_BY, equalTo(task.getAuthor()))
                .body(CREATED_DATE, hasToString(task.getCreatedDate()));
    }

    @Test
    public void testTaskCreatorCanChangeTaskText() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        String newText = "new comment text";

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, newText))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body(ID, longEqual(task.getId()))
                .body(STATE, equalTo(task.getState()))
                .body(TEXT, equalTo(newText))
                .body(ANCHOR_ID, longEqual(task.getCommentId()))
                .body(CREATED_BY, equalTo(task.getAuthor()))
                .body(CREATED_DATE, hasToString(task.getCreatedDate()));
    }

    @Test
    public void testRepoAdminCanChangeTaskText() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        String newText = "new comment text";

        RestAssured
                .given()
                .auth().preemptive().basic(USER_REPO_ADMIN, USER_REPO_ADMIN)
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, newText))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body(ID, longEqual(task.getId()))
                .body(STATE, equalTo(task.getState()))
                .body(TEXT, equalTo(newText))
                .body(ANCHOR_ID, longEqual(task.getCommentId()))
                .body(CREATED_BY, equalTo(task.getAuthor()))
                .body(CREATED_DATE, hasToString(task.getCreatedDate()));
    }

    @Test
    public void testUpdatingPreservesTextAsPlainButStripsWhiteSpace() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        String wikiText = " this is some\n" +
                "\n" +
                "``` \n" +
                "text in markdown\n" +
                "```\n";
        String plainText = "this is some ``` text in markdown ```";

        RestAssured
                .given()
                .auth().preemptive().basic(USER_PR_AUTHOR, USER_PR_AUTHOR)
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, wikiText))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body(ID, longEqual(task.getId()))
                .body(TEXT, equalTo(plainText));

        RestAssured
                .given()
                .auth().preemptive().basic(USER_PR_AUTHOR, USER_PR_AUTHOR)
                .when()
                .get(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body(ID, longEqual(task.getId()))
                .body(TEXT, equalTo(plainText));
    }

    @Test
    public void testCannotChangeTaskTextWhenResolved() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        task.resolve();
        String newText = "new comment text";

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, newText))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(CONFLICT.getStatusCode())
                .body(FIRST_ERROR_MESSAGE, equalTo("You cannot edit a resolved task."));
    }

    @Test
    public void testCannotChangeTaskTextWhenDeleted() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        task.delete();
        String newText = "new comment text";

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, newText))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(NOT_FOUND.getStatusCode())
                .body(FIRST_ERROR_MESSAGE, startsWith("No such task:"));
    }

    @Test
    public void testOtherParticipantCannotChangeTaskText() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        String newText = "new comment text";

        RestAssured
                .given()
                .auth().preemptive().basic(USER_PARTICIPANT, USER_PARTICIPANT)
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, newText))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(UNAUTHORIZED.getStatusCode())
                .body(FIRST_ERROR_MESSAGE, equalTo("You do not have permission to edit this task."));
    }

    @Test
    public void testUpdateTextByOtherParticipantShouldThrowUnAuthorizedEvenIfTaskIsResolved() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        String newText = "new comment text";
        task.resolve();

        RestAssured
                .given()
                .auth().preemptive().basic(USER_PARTICIPANT, USER_PARTICIPANT)
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, newText))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(UNAUTHORIZED.getStatusCode())
                .body(FIRST_ERROR_MESSAGE, equalTo("You do not have permission to edit this task."));
    }

    @Test
    public void testAnonymousUserCannotChangeTaskText() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        String newText = "new comment text";

        RestAssured
                .given()
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, newText))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(UNAUTHORIZED.getStatusCode())
                .body("message", equalTo("You are not permitted to access this resource"));
    }

    @Test
    public void testCannotChangeTaskThatDoesNotExist() throws Exception {
        long taskId = Long.MAX_VALUE;
        String newText = "new comment text";

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.of(ID, taskId,
                        TEXT, newText))
                .contentType(ContentType.JSON)
                .when()
                .put(TaskTestHelper.getResourceUrl(taskId))
                .then()
                .log().ifValidationFails()
                .statusCode(NOT_FOUND.getStatusCode())
                .body(FIRST_ERROR_MESSAGE, startsWith("No such task:"));
    }

    @Test
    public void testParticipantCanResolveTask() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);

        RestAssured
                .given()
                .auth().preemptive().basic(USER_PARTICIPANT, USER_PARTICIPANT)
                .body(ImmutableMap.of(ID, task.getId(),
                        STATE, TaskState.RESOLVED.name()))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode())
                .body(ID, longEqual(task.getId()))
                .body(STATE, equalTo(TaskState.RESOLVED.name()))
                .body(TEXT, equalTo(task.getText()))
                .body(ANCHOR_ID, longEqual(task.getCommentId()))
                .body(CREATED_BY, equalTo(task.getAuthor()))
                .body(CREATED_DATE, hasToString(task.getCreatedDate()));
    }

    @Test
    public void testParticipantCanUnResolveTask() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        task.resolve();

        RestAssured
                .given()
                .auth().preemptive().basic(USER_PARTICIPANT, USER_PARTICIPANT)
                .body(ImmutableMap.of(ID, task.getId(),
                        STATE, TaskState.OPEN.name()))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode());
    }

    @Test
    public void testAnonymousUserCannotResolveTask() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);

        RestAssured
                .given()
                .body(ImmutableMap.of(ID, task.getId(),
                        STATE, TaskState.RESOLVED.name()))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .body("message", equalTo("You are not permitted to access this resource"))
                .statusCode(UNAUTHORIZED.getStatusCode());
    }

    @Test
    public void testCannotResolveTaskWhenUnableToSeePullRequest() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);

        RestAssured
                .given()
                .auth().preemptive().basic(USER_CANNOT_READ, USER_CANNOT_READ)
                .body(ImmutableMap.of(ID, task.getId(),
                        STATE, TaskState.RESOLVED.name()))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(NOT_FOUND.getStatusCode());
    }

    @Test
    public void testCannotResolveDeletedTask() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        task.delete();

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.of(ID, task.getId(),
                        STATE, TaskState.RESOLVED.name()))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(NOT_FOUND.getStatusCode());
    }

    @Test
    public void testCannotUnResolveDeletedTask() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);
        task.delete();

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.of(ID, task.getId(),
                        STATE, TaskState.OPEN.name()))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(NOT_FOUND.getStatusCode());
    }

    @Test
    public void testCannotUpdateWithEmptyText() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, ""))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .body(FIRST_ERROR_MESSAGE, equalTo("Text must not be empty when updating a task."))
                .statusCode(BAD_REQUEST.getStatusCode());
    }

    @Test
    public void testCannotUpdateWithWhiteSpaceOnlyText() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.of(ID, task.getId(),
                        TEXT, " \n"))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .body(FIRST_ERROR_MESSAGE, equalTo("Text must not be empty when updating a task."))
                .statusCode(BAD_REQUEST.getStatusCode());
    }

    @Test
    public void testCannotUpdateIfMismatchedId() throws Exception {
        TaskTestHelper task = createTask(testContext, pullRequest);

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.of(ID, task.getId() + 1,
                        TEXT, "mismatch"))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .body(FIRST_ERROR_MESSAGE, startsWith("Task IDs do not match:"))
                .statusCode(BAD_REQUEST.getStatusCode());
    }

    @Test
    public void testIgnoresUpdatesToAnchorAuthorAndCreatedDate() throws Exception {
        PullRequestComment comment1 = createComment(pullRequest);
        PullRequestComment comment2 = createComment(pullRequest);
        TaskTestHelper task = createTask(testContext, comment1.pullRequestId, comment1.commentId);

        RestAssured
                .given()
                .auth().preemptive().basic(USER_TASK_CREATOR, USER_TASK_CREATOR)
                .body(ImmutableMap.of(ID, task.getId(),
                        ANCHOR, ImmutableMap.of(ID, comment2.commentId,
                                ANCHOR_TYPE, TaskAnchorType.COMMENT),
                        AUTHOR, ImmutableMap.of("name", USER_REPO_ADMIN),
                        CREATED_DATE, new Date()))
                .contentType(ContentType.JSON)
                .when()
                .put(task.getResourceUrl())
                .then()
                .log().ifValidationFails()
                .statusCode(OK.getStatusCode());
    }

}
