package com.atlassian.stash.internal.pull.comment;

import com.atlassian.bitbucket.comment.CommentOperations;
import com.atlassian.stash.internal.comment.InternalComment;
import com.atlassian.stash.internal.comment.InternalCommentable;
import com.atlassian.stash.internal.repository.InternalRepository;
import com.atlassian.stash.internal.task.InternalTaskService;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Collections;

import static org.mockito.Matchers.*;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class OperationCommentPostProcessorTest {

    @Mock
    private InternalCommentable commentable;
    @Mock
    private PermissionService permissionService;
    @Mock
    private AuthenticationContext authenticationContext;
    @Mock
    private InternalApplicationUser user;
    @Mock
    private InternalComment comment;
    @Mock
    private InternalRepository repo;
    @Mock
    private InternalApplicationUser otherUser;
    @Mock
    private InternalTaskService taskService;
    @InjectMocks
    private OperationCommentPostProcessor postProcessor;

    @Before
    public void setupMocks() {
        when(authenticationContext.getCurrentUser()).thenReturn(user);
        when(commentable.getScopeRepository()).thenReturn(repo);
        when(user.getId()).thenReturn(1);
    }

    @Test
    public void testCommentAdminAuthorsCanEditAndDelete() {
        when(comment.getAuthor()).thenReturn(user);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(true);

        postProcessor.process(commentable, comment);

        verify(comment).setPermittedOperations(editableDeletable(true, true));
    }

    @Test
    public void testCommentNonAuthorAdminsCanDeleteButNotEdit() {
        when(comment.getAuthor()).thenReturn(otherUser);
        when(otherUser.getId()).thenReturn(2);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(true);

        postProcessor.process(commentable, comment);

        verify(comment).setPermittedOperations(editableDeletable(false, true));
    }

    @Test
    public void testCommentAuthorNonAdminsCanEditAndDelete() {
        when(comment.getAuthor()).thenReturn(user);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);

        postProcessor.process(commentable, comment);

        verify(comment).setPermittedOperations(editableDeletable(true, true));
    }

    @Test
    public void testCommentNonAuthorNonAdminsCanNeitherDeleteNorEdit() {
        when(comment.getAuthor()).thenReturn(otherUser);
        when(otherUser.getId()).thenReturn(2);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);

        postProcessor.process(commentable, comment);

        verify(comment).setPermittedOperations(editableDeletable(false, false));
    }

    @Test
    public void testAnchorAdminAuthorsCanEditAndDelete() {
        when(comment.getAuthor()).thenReturn(user);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(true);
        postProcessor.processAll(commentable, Collections.singleton(comment));

        verify(comment).setPermittedOperations(editableDeletable(true, true));
    }

    @Test
    public void testAnchorNonAuthorAdminsCanDeleteButNotEdit() {
        when(comment.getAuthor()).thenReturn(otherUser);
        when(otherUser.getId()).thenReturn(2);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(true);

        postProcessor.processAll(commentable, Collections.singleton(comment));

        verify(comment).setPermittedOperations(editableDeletable(false, true));
    }

    @Test
    public void testAnchorAuthorNonAdminsCanEditAndDelete() {
        when(comment.getAuthor()).thenReturn(user);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);

        postProcessor.processAll(commentable, Collections.singleton(comment));

        verify(comment).setPermittedOperations(editableDeletable(true, true));
    }

    @Test
    public void testCommentShouldBeDeletableWhenItHasTasks() throws Exception {
        when(taskService.hasTasks(eq(comment))).thenReturn(true);
        when(comment.getAuthor()).thenReturn(user);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);

        postProcessor.processAll(commentable, Collections.singleton(comment));

        verify(comment).setPermittedOperations(editableDeletable(true, true));
    }

    @Test
    public void testAnchorNonAuthorNonAdminsCanNeitherDeleteNorEdit() {
        when(comment.getAuthor()).thenReturn(otherUser);
        when(otherUser.getId()).thenReturn(2);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);

        postProcessor.processAll(commentable, Collections.singleton(comment));

        verify(comment).setPermittedOperations(editableDeletable(false, false));
    }

    private CommentOperations editableDeletable(final boolean editable, final boolean deletable) {
        return argThat(new BaseMatcher<CommentOperations>() {
            @Override
            public boolean matches(Object o) {
                return !(editable ^ ((CommentOperations) o).isEditable())
                        && !(deletable ^ ((CommentOperations) o).isDeletable());
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("Comment should be" + (editable ? " " : " !") + "editable and" + (deletable ? " " : " !") + "deletable");
            }
        });
    }
}
