package com.atlassian.stash.internal.discovery;

import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.request.RequestContext;
import com.atlassian.bitbucket.request.RequestManager;
import com.atlassian.bitbucket.scm.ScmRequest;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.util.MoreCollectors;
import com.atlassian.sal.api.usersettings.UserSettings;
import com.atlassian.sal.api.usersettings.UserSettingsBuilder;
import com.atlassian.sal.core.usersettings.DefaultUserSettings;
import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.user.InternalUserSettingsService;
import com.google.common.base.Function;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

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

@RunWith(MockitoJUnitRunner.class)
public class DefaultFeatureDiscoveryServiceTest {

    @Mock
    private AuthenticationContext authenticationContext;
    @Mock
    private ApplicationUser currentUser;
    @Mock
    private HttpServletRequest request;
    @Mock
    private RequestContext requestContext;
    @Mock
    private RequestManager requestManager;
    @Mock
    private HttpSession session;
    @Mock
    private InternalUserSettingsService userSettingsService;

    @InjectMocks
    private DefaultFeatureDiscoveryService service;

    @Before
    public void setup() {
        when(authenticationContext.getCurrentUser()).thenReturn(currentUser);
        when(requestManager.getRequestContext()).thenReturn(null);
    }

    @Test
    public void testGetUndiscoveredEmptyKeys() {
        when(userSettingsService.getUserSettings(currentUser)).thenReturn(DefaultUserSettings.builder().build());

        Set<DiscoverableFeature> undiscovered = service.getUndiscovered();
        assertTrue(undiscovered.contains(DiscoverableFeature.COMMIT_REVIEW));
        assertTrue(undiscovered.contains(DiscoverableFeature.NEEDS_WORK));
    }

    @Test
    public void testGetUndiscoveredEmptyKeysWithFilter() {
        when(userSettingsService.getUserSettings(currentUser)).thenReturn(DefaultUserSettings.builder().build());

        Set<DiscoverableFeature> undiscovered = service.getIfUndiscovered(EnumSet.of(DiscoverableFeature.COMMIT_REVIEW));
        assertTrue(undiscovered.contains(DiscoverableFeature.COMMIT_REVIEW));
        assertFalse(undiscovered.contains(DiscoverableFeature.NEEDS_WORK));
    }

    @Test
    public void testGetUndiscoveredWithKeysSet() {
        //All of these keys if they are present their value is 'true'
        when(userSettingsService.getUserSettings(currentUser)).thenReturn(DefaultUserSettings.builder()
                .put(DiscoverableFeature.NEEDS_WORK.getKey(), true)
                .put(DiscoverableFeature.COMMIT_REVIEW.getKey(), true)
                .build());

        Set<DiscoverableFeature> undiscovered = service.getUndiscovered();
        assertFalse(undiscovered.contains(DiscoverableFeature.COMMIT_REVIEW));
        assertFalse(undiscovered.contains(DiscoverableFeature.NEEDS_WORK));
    }

    @Test
    public void testGetUndiscoveredWithLegacyKeysSet() {
        when(userSettingsService.getUserSettings(currentUser)).thenReturn(DefaultUserSettings.builder()
                .put(DiscoverableFeature.NEEDS_WORK.getLegacyKey().get(), true)
                .build());

        Set<DiscoverableFeature> undiscovered = service.getUndiscovered();
        assertFalse(undiscovered.contains(DiscoverableFeature.NEEDS_WORK));
    }

    @Test
    public void testGetUndiscoveredWithVersionKey() {
        when(userSettingsService.getUserSettings(currentUser)).thenReturn(DefaultUserSettings.builder()
                .put(ApplicationConstants.USER_CREATED_VERSION, "4.8.0").build());

        Set<DiscoverableFeature> undiscovered = service.getUndiscovered();
        assertFalse(undiscovered.contains(DiscoverableFeature.COMMIT_REVIEW));
        assertFalse(undiscovered.contains(DiscoverableFeature.NEEDS_WORK));
    }

    @Test
    public void testGetUndiscoveredIfSessionHasAllDiscoveredFlag() {
        when(requestManager.getRequestContext()).thenReturn(requestContext);
        when(requestContext.getRawRequest()).thenReturn(request);
        when(request.getSession(true)).thenReturn(session);
        when(session.getAttribute("feature.discovery.all-discovered")).thenReturn(Boolean.TRUE);

        assertTrue(service.getUndiscovered().isEmpty());
        verifyZeroInteractions(userSettingsService);
    }

    @Test
    public void testGetUndiscoveredWithoutASession() {
        when(requestManager.getRequestContext()).thenReturn(requestContext);
        when(requestContext.getRawRequest()).thenReturn(request);
        when(request.getSession(true)).thenReturn(null);
        when(userSettingsService.getUserSettings(currentUser)).thenReturn(DefaultUserSettings.builder().build());

        Set<DiscoverableFeature> undiscovered = service.getUndiscovered();
        assertTrue(undiscovered.contains(DiscoverableFeature.COMMIT_REVIEW));
        assertTrue(undiscovered.contains(DiscoverableFeature.NEEDS_WORK));
    }

    @Test
    public void testGetUndiscoveredWithNonHttpRequest() {
        ScmRequest scmRequest = mock(ScmRequest.class);
        when(requestManager.getRequestContext()).thenReturn(requestContext);
        when(requestContext.getRawRequest()).thenReturn(scmRequest);

        when(userSettingsService.getUserSettings(currentUser)).thenReturn(DefaultUserSettings.builder().build());

        Set<DiscoverableFeature> undiscovered = service.getUndiscovered();
        assertTrue(undiscovered.contains(DiscoverableFeature.COMMIT_REVIEW));
        assertTrue(undiscovered.contains(DiscoverableFeature.NEEDS_WORK));
    }

    @Test
    public void testSetDiscovered() {
        service.setDiscovered(EnumSet.of(DiscoverableFeature.COMMIT_REVIEW, DiscoverableFeature.NEEDS_WORK));

        assertKeysWereUpdated(DiscoverableFeature.COMMIT_REVIEW, DiscoverableFeature.NEEDS_WORK);
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testSetDiscoveredEmptySet() {
        service.setDiscovered(EnumSet.noneOf(DiscoverableFeature.class));
        verify(userSettingsService, never()).updateUserSettings(any(ApplicationUser.class), any(Function.class));
    }

    @SuppressWarnings({"unchecked", "Guava"})
    private void assertKeysWereUpdated(DiscoverableFeature... features) {
        ArgumentCaptor<Function> captor = ArgumentCaptor.forClass(Function.class);
        verify(userSettingsService).updateUserSettings(same(currentUser), captor.capture());

        UserSettingsBuilder builder = DefaultUserSettings.builder();
        captor.getValue().apply(builder);

        UserSettings actual = builder.build();
        List<String> updatedKeys = Arrays.stream(features)
                .map(DiscoverableFeature::getKey)
                .collect(MoreCollectors.toImmutableList());

        assertEquals(updatedKeys.size(), actual.getKeys().size());
        updatedKeys.forEach(key -> assertTrue(actual.getBoolean(key).getOrElse(false)));
    }
}