package com.atlassian.stash.internal.notification;

import com.atlassian.bitbucket.notification.pull.*;
import com.atlassian.bitbucket.pull.PullRequestParticipant;
import com.atlassian.stash.internal.commit.InternalCommitDiscussion;
import com.atlassian.stash.internal.pull.InternalPullRequest;
import com.atlassian.stash.internal.repository.InternalRepository;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.atlassian.stash.internal.watcher.InternalWatcher;
import com.atlassian.stash.internal.watcher.InternalWatcherService;
import com.atlassian.bitbucket.notification.commit.CommitDiscussionCommentAddedNotification;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.watcher.Watcher;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
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 java.util.List;

import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.IsCollectionContaining.hasItems;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class WatcherNotificationAddresserTest {

    @Mock
    private InternalWatcherService internalWatcherService;

    @Mock
    private PermissionService permissionService;

    @InjectMocks
    private WatcherNotificationAddresser watcherPullRequestNotificationAddresser;

    @Test
    public void testRecipientsForPullRequestNotification() {
        InternalPullRequest pullRequest = mock(InternalPullRequest.class);
        ApplicationUser user = mock(InternalApplicationUser.class);
        Watcher watcher = mock(InternalWatcher.class);
        InternalRepository repository = mock(InternalRepository.class);

        CommentAddedPullRequestNotification notification = mock(CommentAddedPullRequestNotification.class);

        when(watcher.getUser()).thenReturn(user);
        when(user.getEmailAddress()).thenReturn("example@example.com");
        doReturn(Collections.singleton(watcher)).when(internalWatcherService).findWatchers(pullRequest);

        when(notification.getPullRequest()).thenReturn(pullRequest);
        when(pullRequest.getScopeRepository()).thenReturn(repository);

        when(permissionService.hasGlobalPermission(user, Permission.LICENSED_USER)).thenReturn(true);
        when(permissionService.hasRepositoryPermission(user, repository, Permission.REPO_READ)).thenReturn(true);

        assertThat(watcherPullRequestNotificationAddresser.getRecipients(notification), hasItems((ApplicationUser) user));
    }

    @Test
    public void testRecipientsForCommitDiscussionNotification() {
        InternalCommitDiscussion commitDiscussion = mock(InternalCommitDiscussion.class);
        ApplicationUser user = mock(InternalApplicationUser.class);
        Watcher watcher = mock(InternalWatcher.class);
        InternalRepository repository = mock(InternalRepository.class);

        CommitDiscussionCommentAddedNotification notification = mock(CommitDiscussionCommentAddedNotification.class);

        when(watcher.getUser()).thenReturn(user);
        when(user.getEmailAddress()).thenReturn("example@example.com");
        doReturn(Collections.singleton(watcher)).when(internalWatcherService).findWatchers(commitDiscussion);

        when(notification.getCommitDiscussion()).thenReturn(commitDiscussion);
        when(commitDiscussion.getScopeRepository()).thenReturn(repository);

        when(permissionService.hasGlobalPermission(user, Permission.LICENSED_USER)).thenReturn(true);
        when(permissionService.hasRepositoryPermission(user, repository, Permission.REPO_READ)).thenReturn(true);

        assertThat(watcherPullRequestNotificationAddresser.getRecipients(notification), hasItems((ApplicationUser) user));
    }

    @Test
    public void testPullRequestReviewerAddedNotification() {
        ApplicationUser user = mock(InternalApplicationUser.class);
        List<ApplicationUser> reviewers = Lists.newArrayList(user);

        PullRequestReviewerAddedNotification notification = mock(PullRequestReviewerAddedNotification.class);

        when(notification.getAddedReviewers()).thenReturn(reviewers);

        assertThat(watcherPullRequestNotificationAddresser.getRecipients(notification), hasItems(user));
    }

    @Test
    public void testPullRequestReviewedNotification() {
        testNotification(mock(PullRequestReviewedNotification.class), true, false);
    }

    @Test
    public void testPullRequestApprovedNotification() {
        testNotification(mock(PullRequestApprovedNotification.class), true, true);
    }

    private void testNotification(PullRequestNotification notification, boolean authorIsRecipient, boolean
            reviewerIsRecipient) {
        PullRequestParticipant reviewer = mock(PullRequestParticipant.class);
        InternalApplicationUser reviewerUser = mock(InternalApplicationUser.class);
        Watcher reviewerWatcher = mock(InternalWatcher.class);
        when(reviewerUser.getEmailAddress()).thenReturn("reviewer@example.com");
        when(reviewer.getUser()).thenReturn(reviewerUser);
        when(reviewerWatcher.getUser()).thenReturn(reviewerUser);

        PullRequestParticipant author = mock(PullRequestParticipant.class);
        InternalApplicationUser authorUser = mock(InternalApplicationUser.class);
        Watcher authorWatcher = mock(InternalWatcher.class);
        String authorEmail = "author@example.com";
        when(authorUser.getEmailAddress()).thenReturn(authorEmail);
        when(author.getUser()).thenReturn(authorUser);
        when(authorWatcher.getUser()).thenReturn(authorUser);

        InternalPullRequest pullRequest = mock(InternalPullRequest.class);
        InternalRepository repository = mock(InternalRepository.class);

        when(pullRequest.getAuthor()).thenReturn(author);
        when(pullRequest.getReviewers()).thenReturn(Collections.singleton(reviewer));
        when(pullRequest.getScopeRepository()).thenReturn(repository);

        doReturn(ImmutableSet.of(authorWatcher, reviewerWatcher)).when(internalWatcherService).findWatchers(pullRequest);
        when(notification.getPullRequest()).thenReturn(pullRequest);
        when(permissionService.hasGlobalPermission(authorUser, Permission.LICENSED_USER)).thenReturn(true);
        when(permissionService.hasGlobalPermission(reviewerUser, Permission.LICENSED_USER)).thenReturn(true);
        when(permissionService.hasRepositoryPermission(authorUser, repository, Permission.REPO_READ)).thenReturn(true);
        when(permissionService.hasRepositoryPermission(reviewerUser, repository, Permission.REPO_READ)).thenReturn(true);

        Iterable<ApplicationUser> recipients = watcherPullRequestNotificationAddresser.getRecipients(notification);
        if (authorIsRecipient || reviewerIsRecipient) {
            assertTrue("No recipients", recipients.iterator().hasNext());
            assertThat(recipients, authorIsRecipient ? hasItems(authorUser) : not(hasItems(authorUser)));
            assertThat(recipients, reviewerIsRecipient ? hasItems(reviewerUser) : not(hasItems(reviewerUser)));
        }
    }
}
