package com.atlassian.stash.internal.notification.commit;

import com.atlassian.bitbucket.comment.Comment;
import com.atlassian.bitbucket.commit.CommitDiscussion;
import com.atlassian.bitbucket.commit.CommitDiscussionCommentAnchor;
import com.atlassian.bitbucket.event.ApplicationEvent;
import com.atlassian.bitbucket.event.EventTestUtils;
import com.atlassian.bitbucket.event.commit.CommitDiscussionCommentAddedEvent;
import com.atlassian.bitbucket.event.commit.CommitDiscussionCommentRepliedEvent;
import com.atlassian.bitbucket.event.commit.CommitDiscussionEvent;
import com.atlassian.bitbucket.notification.Notification;
import com.atlassian.bitbucket.notification.commit.CommitDiscussionCommentAddedNotification;
import com.atlassian.bitbucket.notification.commit.CommitDiscussionCommentRepliedNotification;
import com.atlassian.bitbucket.notification.pull.CommitCommentAddedPullRequestNotification;
import com.atlassian.bitbucket.notification.pull.CommitCommentRepliedPullRequestNotification;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.stash.internal.commit.InternalCommitService;
import com.atlassian.stash.internal.notification.NotificationManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.List;
import java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class CommitDiscussionNotificationProducerTest {

    private static final long COMMENT_ID = 99;

    @InjectMocks
    private CommitDiscussionNotificationProducer commitDiscussionNotificationProducer;

    @Mock
    private NotificationManager notificationManager;

    @Mock
    private CommitDiscussion commitDiscussion;

    @Mock
    private InternalCommitService commitService;

    @Mock
    private Repository repository;

    @Mock
    private ApplicationUser user;

    @Before
    public void setUp() {
        when(commitDiscussion.getRepository()).thenReturn(repository);
    }

    @Test
    public void testCommentAdded() {
        Comment comment = mock(Comment.class);
        CommitDiscussionCommentAddedEvent event =
                setupEvent(new CommitDiscussionCommentAddedEvent(this, commitDiscussion, comment, null, null));
        commitDiscussionNotificationProducer.onCommitDiscussionCommentAdded(event);

        CommitDiscussionCommentAddedNotification notification =
                assertNotification(CommitDiscussionCommentAddedNotification.class, event);
        assertEquals("Same Comment", comment, notification.getComment());
        assertEquals("Same discussion", commitDiscussion, notification.getCommitDiscussion());
    }

    @Test
    public void testCommentAddedOnPullRequest() {
        PullRequest pullRequest = mock(PullRequest.class);
        CommitDiscussionCommentAnchor anchor = mock(CommitDiscussionCommentAnchor.class);
        when(anchor.getPullRequest()).thenReturn(Optional.of(pullRequest));
        when(anchor.getFromHash()).thenReturn("since");
        when(anchor.getToHash()).thenReturn("until");

        Comment comment = mock(Comment.class);
        CommitDiscussionCommentAddedEvent event =
                setupEvent(new CommitDiscussionCommentAddedEvent(this, commitDiscussion, comment, null, anchor));
        commitDiscussionNotificationProducer.onCommitDiscussionCommentAdded(event);

        CommitCommentAddedPullRequestNotification notification =
                assertNotification(CommitCommentAddedPullRequestNotification.class, event);
        assertEquals("Same Comment", comment, notification.getComment());
        assertEquals("Same pull request", pullRequest, notification.getPullRequest());
        assertEquals("Same since commit", "since", notification.getSince());
        assertEquals("Same until commit", "until", notification.getUntil());
    }

    @Test
    public void testCommentReplied() {
        Comment comment = mock(Comment.class);
        Comment parentComment = mock(Comment.class);
        CommitDiscussionCommentRepliedEvent event =
                setupEvent(new CommitDiscussionCommentRepliedEvent(this, commitDiscussion, comment, parentComment));
        commitDiscussionNotificationProducer.onCommitDiscussionCommentReplied(event);

        CommitDiscussionCommentRepliedNotification notification =
                assertNotification(CommitDiscussionCommentRepliedNotification.class, event);
        assertEquals("Same Comment", comment, notification.getComment());
        assertEquals("Same Parent Comment", parentComment, notification.getParent());
        assertEquals("Same discussion", commitDiscussion, notification.getCommitDiscussion());
    }

    @Test
    public void testCommentRepliedOnPullRequest() {
        PullRequest pullRequest = mock(PullRequest.class);
        CommitDiscussionCommentAnchor anchor = mock(CommitDiscussionCommentAnchor.class);
        when(anchor.getPullRequest()).thenReturn(Optional.of(pullRequest));
        when(anchor.getFromHash()).thenReturn("since");
        when(anchor.getToHash()).thenReturn("until");

        Comment comment = mock(Comment.class);
        when(comment.getId()).thenReturn(COMMENT_ID);

        Comment parentComment = mock(Comment.class);
        CommitDiscussionCommentRepliedEvent event =
                setupEvent(new CommitDiscussionCommentRepliedEvent(this, commitDiscussion, comment, parentComment));

        when(commitService.getAnchorByComment(commitDiscussion, COMMENT_ID)).thenReturn(anchor);

        commitDiscussionNotificationProducer.onCommitDiscussionCommentReplied(event);

        CommitCommentRepliedPullRequestNotification notification =
                assertNotification(CommitCommentRepliedPullRequestNotification.class, event);
        assertEquals("Same Comment", comment, notification.getComment());
        assertEquals("Same Parent Comment", parentComment, notification.getParent());
        assertEquals("Same pull request", pullRequest, notification.getPullRequest());
        assertEquals("Same since commit", "since", notification.getSince());
        assertEquals("Same until commit", "until", notification.getUntil());
    }

    private <T extends CommitDiscussionEvent> T setupEvent(T event) {
        EventTestUtils.injectUser(event, user);
        return event;
    }

    private List<Notification> getNotifications(int count) {
        ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class);
        verify(notificationManager, times(count)).dispatch(notificationCaptor.capture());
        return notificationCaptor.getAllValues();
    }

    private <N extends Notification, E extends ApplicationEvent> N assertNotification(Class<N> notificationType,
                                                                                      E event) {
        return assertNotification(notificationType, getNotifications(1).get(0), event);
    }

    private <N extends Notification, E extends ApplicationEvent> N assertNotification(Class<N> notificationType,
                                                                                      Notification notification,
                                                                                      E event) {
        assertEquals("Notification of expected type", notificationType, notification.getClass());
        assertEquals("Same User", event.getUser(), notification.getUser());
        assertEquals("Same Date", event.getDate(), notification.getTimestamp());
        return notificationType.cast(notification);
    }
}
