package com.atlassian.stash.internal.notification.pull.activity;

import com.atlassian.bitbucket.comment.CommentAction;
import com.atlassian.bitbucket.mockito.MockitoUtils;
import com.atlassian.bitbucket.notification.pull.PullRequestActivityNotification;
import com.atlassian.bitbucket.notification.pull.PullRequestUpdatedNotification;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestAction;
import com.atlassian.bitbucket.pull.PullRequestActivity;
import com.atlassian.bitbucket.pull.PullRequestRole;
import com.atlassian.bitbucket.repository.Ref;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.stash.internal.comment.InternalComment;
import com.atlassian.stash.internal.notification.batch.dao.UserNotificationDao;
import com.atlassian.stash.internal.notification.handlers.NotificationHelper;
import com.atlassian.stash.internal.notification.mention.MentionHelper;
import com.atlassian.stash.internal.notification.usersettings.SendMode;
import com.atlassian.stash.internal.pull.*;
import com.atlassian.stash.internal.repository.InternalRepository;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.google.common.collect.ImmutableSet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Date;
import java.util.Set;

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class PullRequestBatchNotificationHandlerTest {

    private static final InternalApplicationUser USER = user(1);
    private static final InternalPullRequest PR = pullRequest(USER);

    @Mock
    private MentionHelper mentionHelper;
    @Mock
    private UserNotificationDao userNotificationDao;
    @Mock
    private NotificationHelper notificationHelper;

    private PullRequestBatchNotificationHandler handler;

    @Before
    public void setup() {
        handler = new PullRequestBatchNotificationHandler(mentionHelper,
                new PullRequestBatchNotificationDao(userNotificationDao), notificationHelper);
        when(notificationHelper.filterBySendMode(any(Iterable.class), eq(ImmutableSet.of(SendMode.BATCHED))))
                .thenAnswer(MockitoUtils.returnFirst());
    }

    @Test
    public void testSendModeFiltering() {
        ApplicationUser user1 = user(1);
        ApplicationUser user2 = user(2);

        Set<ApplicationUser> users = ImmutableSet.of(user1, user2);
        when(notificationHelper.filterBySendMode(users, ImmutableSet.of(SendMode.BATCHED)))
                .thenReturn(ImmutableSet.of(user1));

        PullRequestActivity activity = new InternalPullRequestActivity.Builder(PR).id(3L)
                .action(PullRequestAction.APPROVED).build();
        handler.handle(notification(activity), users);

        verify(userNotificationDao).create(eq(user1.getId()), eq(activity.getCreatedDate()), eq(PullRequestBatchSender.ID),
                anyString(), anyString());
        verify(userNotificationDao, never()).create(eq(user2.getId()), eq(activity.getCreatedDate()), eq(PullRequestBatchSender.ID),
                anyString(), anyString());
    }

    @Test
    public void testQueueActivity() {
        PullRequestActivity activity = new InternalPullRequestActivity.Builder(PR).id(3L)
                .action(PullRequestAction.REVIEWED).build();
        handler.handle(notification(activity), ImmutableSet.of(USER, user(1)));
        verify(userNotificationDao).create(USER.getId(), activity.getCreatedDate(), PullRequestBatchSender.ID,
                batchId(PR), "{\"activity\":" + activity.getId() + "}");
        verifyNoMoreInteractions(userNotificationDao);
    }

    @Test
    public void testQueueActivityOfReviewedOnlyToAuthor() {
        PullRequestActivity activity = new InternalPullRequestActivity.Builder(PR).id(3L)
                .action(PullRequestAction.APPROVED).build();
        handler.handle(notification(activity), ImmutableSet.of(USER));
        verify(userNotificationDao).create(USER.getId(), activity.getCreatedDate(), PullRequestBatchSender.ID,
                batchId(PR), "{\"activity\":" + activity.getId() + "}");
    }

    @Test
    public void testFilterUpdatedActivity() {
        PullRequestActivity activity = new InternalPullRequestActivity.Builder(PR).id(3L)
                .action(PullRequestAction.UPDATED).build();
        handler.handle(notification(activity), ImmutableSet.of(USER));
        verifyZeroInteractions(userNotificationDao);
    }

    @Test
    public void testFilterOpenedActivity() {
        PullRequestActivity activity = new InternalPullRequestActivity.Builder(PR).id(3L)
                .action(PullRequestAction.OPENED).build();
        handler.handle(notification(activity), ImmutableSet.of(USER));
        verifyZeroInteractions(userNotificationDao);
    }

    @Test
    public void testQueueMergeActivity() {
        PullRequestActivity activity = new InternalPullRequestMergeActivity.Builder(PR).id(3L).build();
        handler.handle(notification(activity), ImmutableSet.of(USER));
        verify(userNotificationDao).create(USER.getId(), activity.getCreatedDate(), PullRequestBatchSender.ID,
                batchId(PR), "{\"activity\":" + activity.getId() + "}");
    }

    @Test
    public void testQueueRescopeActivity() {
        PullRequestActivity activity = new InternalPullRequestRescopeActivity.Builder(PR).id(3L)
                .previousFromHash("123")
                .fromHash("456")
                .build();

        handler.handle(notification(activity), ImmutableSet.of(USER));

        verify(userNotificationDao).create(USER.getId(), activity.getCreatedDate(), PullRequestBatchSender.ID,
                batchId(PR), "{\"activity\":" + activity.getId() + "}");
    }

    @Test
    public void testQueueReviewersUpdatedActivity() {
        InternalPullRequestReviewersUpdatedActivity activity = new InternalPullRequestReviewersUpdatedActivity.Builder(PR)
                .user(user(2))
                .build();

        handler.handle(notification(activity), ImmutableSet.of(USER));

        verify(userNotificationDao).create(USER.getId(), activity.getCreatedDate(), PullRequestBatchSender.ID,
                batchId(PR), "{\"activity\":" + activity.getId() + "}");
    }

    @Test
    public void testQueueReviewersUpdatedActivityShouldNotSaveIfAuthorIsResponsibleForIt() {
        InternalPullRequestReviewersUpdatedActivity activity = new InternalPullRequestReviewersUpdatedActivity.Builder(PR)
                .user(USER)
                .build();

        handler.handle(notification(activity), ImmutableSet.of(USER));

        verifyZeroInteractions(userNotificationDao);
    }

    @Test
    public void testFilterEmptyRescopeActivity() {
        PullRequestActivity activity = new InternalPullRequestRescopeActivity.Builder(PR).id(3L)
                .previousFromHash("123")
                .fromHash("123")
                .build();
        handler.handle(notification(activity), ImmutableSet.of(USER));
        verifyZeroInteractions(userNotificationDao);
    }

    @Test
    public void testQueueCommentActivity() {
        ApplicationUser user1 = user(1);
        ApplicationUser user2 = user(2);
        ApplicationUser user3 = user(3);

        InternalComment comment = new InternalComment.Builder().id(3L).build();
        PullRequestActivity activity = new InternalPullRequestCommentActivity.Builder(PR).id(4L).comment(comment)
                .commentAction(CommentAction.ADDED).build();
        PullRequestActivityNotification notification = notification(activity);

        // user3 is mentioned, but not batched, so should not queue any notifications
        ImmutableSet<ApplicationUser> mentionedUsers = ImmutableSet.of(user2, user3);
        when(mentionHelper.findUsers(notification, comment.getText())).thenReturn(mentionedUsers);
        when(notificationHelper.filterBySendMode(mentionedUsers, ImmutableSet.of(SendMode.BATCHED)))
                .thenReturn(ImmutableSet.of(user2));

        // user2 is a recipient but was also mentioned, should not queue same thing twice
        handler.handle(notification, ImmutableSet.of(user1, user2));

        String expectedData = "{\"comment\":" + comment.getId() + "}";
        verify(userNotificationDao).create(user1.getId(), activity.getCreatedDate(), PullRequestBatchSender.ID,
                batchId(PR), expectedData);
        verify(userNotificationDao).create(user2.getId(), activity.getCreatedDate(), PullRequestBatchSender.ID,
                batchId(PR), expectedData);
        verify(userNotificationDao, never()).create(user3.getId(), activity.getCreatedDate(), PullRequestBatchSender.ID,
                batchId(PR), expectedData);
    }

    @Test
    public void testMentionIsOnlyThingThatIsNeededForActivityToBeQueued() {
        ApplicationUser user1 = user(1);
        ApplicationUser user2 = user(2);

        InternalComment comment = new InternalComment.Builder().id(3L).build();
        PullRequestActivity activity = new InternalPullRequestCommentActivity.Builder(PR).id(4L).comment(comment)
                .commentAction(CommentAction.ADDED).build();
        PullRequestActivityNotification notification = notification(activity);

        ImmutableSet<ApplicationUser> mentionedUsers = ImmutableSet.of(user2);
        when(mentionHelper.findUsers(notification, comment.getText())).thenReturn(mentionedUsers);
        when(notificationHelper.filterBySendMode(ImmutableSet.of(user1), ImmutableSet.of(SendMode.BATCHED)))
                .thenReturn(ImmutableSet.<ApplicationUser>of());
        when(notificationHelper.filterBySendMode(mentionedUsers, ImmutableSet.of(SendMode.BATCHED)))
                .thenReturn(ImmutableSet.of(user2));

        handler.handle(notification, ImmutableSet.of(user1));

        String expectedData = "{\"comment\":" + comment.getId() + "}";
        verify(userNotificationDao).create(user2.getId(), activity.getCreatedDate(), PullRequestBatchSender.ID,
                batchId(PR), expectedData);
    }

    @Test
    public void testFilterCommentEditActivity() {
        InternalComment comment = new InternalComment.Builder().id(3L).build();
        PullRequestActivity activity = new InternalPullRequestCommentActivity.Builder(PR).id(4L).comment(comment)
                .commentAction(CommentAction.EDITED).build();
        PullRequestActivityNotification notification = notification(activity);
        when(mentionHelper.findUsers(notification, comment.getText())).thenReturn(ImmutableSet.<ApplicationUser>of());

        handler.handle(notification, ImmutableSet.of(USER));

        verifyZeroInteractions(userNotificationDao);
    }

    @Test
    public void testUpdatedToBranch() {
        Ref previousToBranch = mock(Ref.class);
        Date date = new Date();
        PullRequestUpdatedNotification updatedNotification = new PullRequestUpdatedNotification(
                PR, date, user(2), "", null, previousToBranch);

        handler.handle(updatedNotification, ImmutableSet.of(USER));

        String toBranch = PR.getToRef().getDisplayId();
        String expectedData = "{\"userId\":2,\"toBranch\":\"" + toBranch + "\",\"date\":" + date.getTime() + "}";
        verify(userNotificationDao).create(USER.getId(), updatedNotification.getTimestamp(), PullRequestBatchSender.ID,
                batchId(PR), expectedData);
    }

    private PullRequestActivityNotification notification(PullRequestActivity activity) {
        return new PullRequestActivityNotification(activity, activity.getCreatedDate(), activity.getUser());
    }

    private static InternalPullRequest pullRequest(InternalApplicationUser user) {
        InternalRepository repository = new InternalRepository.Builder().id(1).name("the-one").build();
        InternalPullRequestRef toRef = new InternalPullRequestRef.Builder().repository(repository).displayId("to-branch").build();
        InternalPullRequestParticipant participant = mock(InternalPullRequestParticipant.class);
        when(participant.getUser()).thenReturn(user);
        when(participant.getRole()).thenReturn(PullRequestRole.AUTHOR);
        return new InternalPullRequest.Builder()
                .participant(participant)
                .scopedId(2L)
                .toRef(toRef)
                .build();
    }

    private static String batchId(PullRequest pullRequest) {
        return PullRequestBatchId.of(pullRequest.getToRef().getRepository().getId(), pullRequest.getId()).encode();
    }

    private static InternalApplicationUser user(int id) {
        InternalApplicationUser user = mock(InternalApplicationUser.class);
        when(user.getId()).thenReturn(id);
        return user;
    }
}
