package com.atlassian.stash.internal.comment.like.notification;

import com.atlassian.bitbucket.user.*;
import com.atlassian.bitbucket.comment.Comment;
import com.atlassian.bitbucket.comment.NoSuchCommentException;
import com.atlassian.stash.experimental.pull.ExperimentalPullRequestService;
import com.atlassian.bitbucket.notification.custom.pull.CustomNotificationSection;
import com.atlassian.bitbucket.pull.PullRequest;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

import static com.atlassian.bitbucket.hamcrest.IsBlankString.notBlank;
import static com.atlassian.bitbucket.mockito.MockitoUtils.returnArg;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class CommentLikePullRequestNotificationRendererTest {

    @Mock
    private CommentLikeFilter likeFilter;
    @Mock
    private CommentLikeNotificationRenderer renderer;
    @Mock
    private ExperimentalPullRequestService pullRequestService;
    @Spy
    private SecurityService securityService = new DummySecurityService();
    @Mock
    private UserService userService;

    @InjectMocks
    private CommentLikePullRequestNotificationRenderer notificationRenderer;

    @SuppressWarnings("unchecked")
    @Before
    public void setUp() throws Exception {
        when(likeFilter.filterRemovedLikes(notNull(Multimap.class))).thenAnswer(returnArg(0));
    }

    @Test
    public void testGetId() throws Exception {
        assertThat(notificationRenderer.getId(), is(notBlank()));
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testRender() throws Exception {
        PullRequest pullRequest = createPullRequest(314, 456);
        ApplicationUser recipient = new TestApplicationUser("foo", 271);

        Comment likedComment = createComment(265);
        Date likeDate = new Date();
        ApplicationUser liker = new TestApplicationUser("bar", 828);
        CommentLikePullRequestNotificationEvent event = new CommentLikePullRequestNotificationEvent(this,
                ImmutableList.of(recipient), pullRequest, likedComment, liker, likeDate);

        when(pullRequestService.getComment(314, 456, 265)).thenReturn(likedComment);
        when(userService.getUserById(liker.getId(), true)).thenReturn(liker);

        when(renderer.render(eq(recipient), argThat(likeData(likedComment, likeDate, liker)))).thenReturn("content");

        Map<ApplicationUser, Collection<CustomNotificationSection>> result = notificationRenderer.render(pullRequest,
                ImmutableMap.<ApplicationUser, Collection<CustomNotificationSection>>of(recipient,
                        (Collection) ImmutableList.of(event.getData())));

        assertThat(result.keySet(), hasSize(1));
        assertThat(result, hasKey(recipient));
        Collection<CustomNotificationSection> renderedNotifications = result.get(recipient);
        assertThat(renderedNotifications, hasSize(1));
        CustomNotificationSection renderedNotification = renderedNotifications.iterator().next();
        assertThat(renderedNotification.getContent(), equalTo("content"));
        assertThat(renderedNotification.getDate(), equalTo(likeDate));
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testRenderWhenCommentCannotBeFound() throws Exception {
        PullRequest pullRequest = createPullRequest(314, 456);
        ApplicationUser recipient = new TestApplicationUser("foo", 271);

        Comment likedComment = createComment(265);
        Date likeDate = new Date();
        ApplicationUser liker = new TestApplicationUser("bar", 828);
        CommentLikePullRequestNotificationEvent event = new CommentLikePullRequestNotificationEvent(this,
                ImmutableList.of(recipient), pullRequest, likedComment, liker, likeDate);

        when(pullRequestService.getComment(314, 456, 265)).thenThrow(NoSuchCommentException.class);
        when(userService.getUserById(liker.getId(), true)).thenReturn(liker);

        Map<ApplicationUser, Collection<CustomNotificationSection>> result = notificationRenderer.render(pullRequest,
                ImmutableMap.<ApplicationUser, Collection<CustomNotificationSection>>of(recipient,
                        (Collection) ImmutableList.of(event.getData())));

        assertThat(result.keySet(), is(empty()));
        verifyZeroInteractions(renderer);
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testRenderWhenUserCannotBeFound() throws Exception {
        PullRequest pullRequest = createPullRequest(314, 456);
        ApplicationUser recipient = new TestApplicationUser("foo", 271);

        Comment likedComment = createComment(265);
        Date likeDate = new Date();
        ApplicationUser liker = new TestApplicationUser("bar", 828);
        CommentLikePullRequestNotificationEvent event = new CommentLikePullRequestNotificationEvent(this,
                ImmutableList.of(recipient), pullRequest, likedComment, liker, likeDate);

        when(pullRequestService.getComment(314, 456, 265)).thenReturn(likedComment);

        Map<ApplicationUser, Collection<CustomNotificationSection>> result = notificationRenderer.render(pullRequest,
                ImmutableMap.<ApplicationUser, Collection<CustomNotificationSection>>of(recipient,
                        (Collection) ImmutableList.of(event.getData())));

        assertThat(result.keySet(), is(empty()));
        verifyZeroInteractions(renderer);
    }

    private static Comment createComment(long id) {
        Comment comment = mock(Comment.class);
        when(comment.getId()).thenReturn(id);
        return comment;
    }

    private static PullRequest createPullRequest(int repositoryId, long id) {
        PullRequest pullRequest = mock(PullRequest.class, RETURNS_DEEP_STUBS);
        when(pullRequest.getId()).thenReturn(id);
        when(pullRequest.getToRef().getRepository().getId()).thenReturn(repositoryId);
        return pullRequest;
    }

    private Matcher<CommentLikeData> likeData(final Comment comment, final Date date, final ApplicationUser... likers) {
        return new TypeSafeMatcher<CommentLikeData>() {
            @Override
            protected boolean matchesSafely(CommentLikeData likeData) {
                return comment.equals(likeData.getComment())
                        && date.equals(likeData.getDate())
                        && Iterables.elementsEqual(Arrays.asList(likers), likeData.getLikers());
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("like data");
            }
        };
    }
}