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

import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.permission.PermissionService;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.TestApplicationUser;
import com.atlassian.bitbucket.user.UserService;
import com.atlassian.fugue.Pair;
import com.atlassian.bitbucket.comment.Comment;
import com.atlassian.bitbucket.notification.Notification;
import com.atlassian.stash.internal.notification.handlers.AbstractNotificationHandler;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.bitbucket.util.Chainable;
import com.google.common.collect.Iterables;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

import java.util.Arrays;
import java.util.Set;

import static com.atlassian.stash.internal.notification.mention.MentionHelper.MENTIONS_ENABLED;
import static com.google.common.collect.Lists.*;
import static com.google.common.collect.Sets.newHashSet;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class MentionHelperTest {

    @Mock
    private ApplicationPropertiesService applicationPropertiesService;

    @Mock
    private UserService userService;

    @Mock
    private PermissionService permissionService;

    @Before
    public void setup() {
        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                Set<String> users = (Set) invocation.getArguments()[0];
                return Chainable.chain(users).transform(MentionHelperTest::createUser).toSet();
            }
        }).when(userService).getUsersByName(anySetOf(String.class));
    }

    private MentionHelper createHelper() {
        return new MentionHelper(applicationPropertiesService, MentionScannerTest.createScanner(10), userService, permissionService);
    }

    private MentionHelper createHelperLicensed() {
        return new MentionHelper(applicationPropertiesService, MentionScannerTest.createScanner(10), userService, user -> true);
    }

    @Test
    public void testNotLicensed() {
        addUsers("a");
        assertExactItems(createHelper().findUsers(Mockito.mock(Notification.class), "@a"));
    }

    @Test
    public void testLicensed() {
        addUsers("a");
        addLicense("a");
        assertExactItems(createHelper().findUsers(Mockito.mock(Notification.class), "@a"), createUser("a"));
    }

    @Test
    public void testLicensedNotSelf() {
        addUsers("a", "b");
        addLicense("a");
        addLicense("b");
        assertExactItems(createHelper().findUsers(mockNotification("b"), "@b @a"), createUser("a"));
    }

    private void addLicense(String username) {
        when(permissionService.hasGlobalPermission(createUser(username), Permission.LICENSED_USER)).thenReturn(true);
    }

    private void addUsers(String... usernames) {
        doReturn(newHashSet(transform(newArrayList(usernames), MentionHelperTest::createUser)))
                .when(userService).getUsersByName(newHashSet(usernames));
    }

    private static <T> void assertExactItems(Iterable<T> b, T... a) {
        assertThat(b, hasItems(a));
        assertThat(Iterables.size(b), equalTo(a.length));
    }

    private static Notification mockNotification(String username) {
        Notification notification = Mockito.mock(Notification.class);
        Mockito.when(notification.getUser()).thenReturn(createUser(username));
        return notification;
    }

    private static ApplicationUser createUser(String username) {
        return new TestApplicationUser(username);
    }

    @Test
    public void testFilteredRecipientsAndMentions() {
        Comment comment = Mockito.mock(Comment.class);
        when(comment.getText()).thenReturn("@a and @b are mentioned");

        Notification notification = Mockito.mock(Notification.class);

        when(applicationPropertiesService.getPluginProperty(AbstractNotificationHandler.PLUGIN_PREFIX + MENTIONS_ENABLED, false)).thenReturn(true);

        Pair<Iterable<ApplicationUser>, Set<ApplicationUser>> pair = createHelperLicensed().getFilteredRecipientsAndMentions(
                notification, comment, Arrays.asList(createUser("a"), createUser("c")));
        assertThat(pair.left(), hasItems(createUser("c")));
        assertThat(pair.right(), hasItems(createUser("a"), createUser("b")));
    }

    @Test
    public void testFilteredRecipientsAndMentionsDisabled() {
        Comment comment = Mockito.mock(Comment.class);
        when(comment.getText()).thenReturn("@a and @b are mentioned");

        Notification notification = Mockito.mock(Notification.class);

        when(applicationPropertiesService.getPluginProperty(AbstractNotificationHandler.PLUGIN_PREFIX + MENTIONS_ENABLED, false)).thenReturn(false);

        Pair<Iterable<ApplicationUser>, Set<ApplicationUser>> pair = createHelperLicensed().getFilteredRecipientsAndMentions(
                notification, comment, Arrays.asList(createUser("a"), createUser("c")));
        assertThat(pair.left(), hasItems(createUser("c")));
        assertThat(pair.right(), empty());
    }
}
