package com.atlassian.stash.internal.task;

import com.atlassian.stash.internal.repository.InternalRepository;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.atlassian.bitbucket.task.TaskOperations;
import com.atlassian.bitbucket.task.TaskState;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.google.common.collect.ImmutableList;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

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

@RunWith(MockitoJUnitRunner.class)
public class OperationTaskPostProcessorTest {

    @Mock
    private AuthenticationContext authenticationContext;
    @Mock
    private InternalTaskContext context;
    @Mock
    private InternalApplicationUser otherUser;
    @Mock
    private PermissionService permissionService;
    @Mock
    private InternalRepository repo;
    @Mock
    private InternalTask task;
    @Mock
    private InternalApplicationUser user;

    private TaskPermissionsHelper taskPermissionsHelper;

    private OperationTaskPostProcessor postProcessor;

    @Before
    public void setupMocks() {
        when(authenticationContext.getCurrentUser()).thenReturn(user);
        when(context.getScopeRepository()).thenReturn(repo);
        when(user.getId()).thenReturn(1);
        taskPermissionsHelper = new TaskPermissionsHelper(authenticationContext, permissionService);
        postProcessor = new OperationTaskPostProcessor(taskPermissionsHelper);
    }

    @Test
    public void testTaskAuthorCanEditDeleteAndTransition() {
        when(task.getAuthor()).thenReturn(user);
        when(task.getContext()).thenReturn(context);
        when(task.getState()).thenReturn(TaskState.OPEN);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);

        postProcessor.process(task);

        verify(task).setPermittedOperations(deletableEditableTransitional(true, true, true));
    }

    @Test
    public void testTaskAuthorCanEditDeleteAndTransitionForListOfTasks() {
        when(task.getAuthor()).thenReturn(user);
        when(task.getState()).thenReturn(TaskState.OPEN);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);

        postProcessor.processAll(context, ImmutableList.of(task));

        verify(task).setPermittedOperations(deletableEditableTransitional(true, true, true));
    }

    @Test
    public void testContextCreatorCanEditDeleteAndTransition() {
        when(task.getAuthor()).thenReturn(otherUser);
        when(task.getContext()).thenReturn(context);
        when(task.getState()).thenReturn(TaskState.OPEN);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);
        when(context.getCreatedBy()).thenReturn(user);

        postProcessor.process(task);

        verify(task).setPermittedOperations(deletableEditableTransitional(true, true, true));
    }

    @Test
    public void testContextCreatorCanEditDeleteAndTransitionForListOfTasks() {
        when(task.getAuthor()).thenReturn(otherUser);
        when(task.getState()).thenReturn(TaskState.OPEN);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);
        when(context.getCreatedBy()).thenReturn(user);

        postProcessor.processAll(context, ImmutableList.of(task));

        verify(task).setPermittedOperations(deletableEditableTransitional(true, true, true));
    }

    @Test
    public void testRepositoryOwnerCanEditDeleteAndTransition() {
        when(task.getAuthor()).thenReturn(otherUser);
        when(task.getContext()).thenReturn(context);
        when(task.getState()).thenReturn(TaskState.OPEN);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(true);
        when(context.getCreatedBy()).thenReturn(otherUser);

        postProcessor.process(task);

        verify(task).setPermittedOperations(deletableEditableTransitional(true, true, true));
    }

    @Test
    public void testRepositoryOwnerCanEditDeleteAndTransitionListOfTasks() {
        when(task.getAuthor()).thenReturn(otherUser);
        when(task.getState()).thenReturn(TaskState.OPEN);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(true);
        when(context.getCreatedBy()).thenReturn(otherUser);

        postProcessor.processAll(context, ImmutableList.of(task));

        verify(task).setPermittedOperations(deletableEditableTransitional(true, true, true));
    }

    @Test
    public void testOtherUserCannotEditAndDeleteCanTransition() {
        when(task.getAuthor()).thenReturn(otherUser);
        when(task.getContext()).thenReturn(context);
        when(task.getState()).thenReturn(TaskState.OPEN);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);
        when(context.getCreatedBy()).thenReturn(otherUser);

        postProcessor.process(task);

        verify(task).setPermittedOperations(deletableEditableTransitional(false, false, true));
    }

    @Test
    public void testOtherUserCannotEditAndDeleteCanTransitionForListOfTasks() {
        when(task.getAuthor()).thenReturn(otherUser);
        when(task.getState()).thenReturn(TaskState.OPEN);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);
        when(context.getCreatedBy()).thenReturn(otherUser);

        postProcessor.processAll(context, ImmutableList.of(task));

        verify(task).setPermittedOperations(deletableEditableTransitional(false, false, true));
    }

    @Test
    public void testUserCannotEditAndDeleteResolvedTasks() {
        when(task.getAuthor()).thenReturn(user);
        when(task.getContext()).thenReturn(context);
        when(task.getState()).thenReturn(TaskState.RESOLVED);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);
        when(context.getCreatedBy()).thenReturn(user);

        postProcessor.process(task);

        verify(task).setPermittedOperations(deletableEditableTransitional(false, false, true));
    }

    @Test
    public void testUserCannotEditAndDeleteResolvedTasksForAListOfTasks() {
        when(task.getAuthor()).thenReturn(user);
        when(task.getState()).thenReturn(TaskState.RESOLVED);
        when(permissionService.hasRepositoryPermission(same(repo), eq(Permission.REPO_ADMIN))).thenReturn(false);
        when(context.getCreatedBy()).thenReturn(user);

        postProcessor.processAll(context, ImmutableList.of(task));

        verify(task).setPermittedOperations(deletableEditableTransitional(false, false, true));
    }

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

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

}