package com.atlassian.stash.internal.user;

import com.atlassian.event.api.EventPublisher;
import com.atlassian.plugin.event.events.PluginFrameworkShuttingDownEvent;
import com.atlassian.plugin.event.events.PluginFrameworkStartedEvent;
import com.atlassian.plugin.osgi.container.OsgiContainerManager;
import com.atlassian.bitbucket.permission.EffectivePermissionsChangedEvent;
import com.atlassian.bitbucket.permission.EffectivePermissionsProvider;
import com.google.common.collect.Sets;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;

import java.util.Collections;

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

@RunWith(MockitoJUnitRunner.class)
public class DefaultEffectivePermissionsProviderTrackerTest {
    @Mock
    private Bundle systemBundle;
    @Mock
    private BundleContext bundleContext;
    @Mock
    private OsgiContainerManager containerManager;
    @Mock
    private EventPublisher eventPublisher;
    @Mock
    private ServiceTracker<EffectivePermissionsProvider, EffectivePermissionsProvider> serviceTracker;
    @InjectMocks
    private DefaultEffectivePermissionsProviderTracker tracker;

    @Before
    public void setUp() throws Exception {
        when(containerManager.getServiceTracker(EffectivePermissionsProvider.class.getName(), tracker)).thenReturn(serviceTracker);
        when(containerManager.getBundles()).thenReturn(new Bundle[] {systemBundle});
        when(systemBundle.getBundleContext()).thenReturn(bundleContext);
    }

    @Test
    public void testOpensTrackerWhenStartedUp() throws Exception {
        tracker.onPluginFrameworkStarted(mock(PluginFrameworkStartedEvent.class));
        verify(bundleContext).addServiceListener(any(ServiceListener.class), anyString());
    }

    @Test
    public void testClosesTrackerWhenShuttingDown() throws Exception {
        tracker.onPluginFrameworkStarted(mock(PluginFrameworkStartedEvent.class));
        tracker.onPluginFrameworkShuttingDown(mock(PluginFrameworkShuttingDownEvent.class));
        verify(bundleContext).removeServiceListener(any(ServiceListener.class));
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testTracksPermissionProviderLifecycle() throws Exception {
        ServiceReference<EffectivePermissionsProvider> reference1 = mock(ServiceReference.class);
        EffectivePermissionsProvider provider1 = mockProviderReference(reference1);

        ServiceReference<EffectivePermissionsProvider> reference2 = mock(ServiceReference.class);
        EffectivePermissionsProvider provider2 = mockProviderReference(reference2);

        tracker.onPluginFrameworkStarted(mock(PluginFrameworkStartedEvent.class));
        tracker.addingService(reference1);

        assertEquals(Collections.singleton(provider1), tracker.getProviders());
        verifyEventPublished();
        verify(bundleContext).getService(reference1);

        tracker.addingService(reference2);

        assertEquals(Sets.newHashSet(provider1, provider2), tracker.getProviders());
        verifyEventPublished();
        verify(bundleContext).getService(reference2);

        tracker.removedService(reference1, provider1);

        assertEquals(Sets.newHashSet(provider2), tracker.getProviders());
        verifyEventPublished();
        verify(bundleContext).ungetService(reference1);

        tracker.removedService(reference2, provider2);

        assertTrue(tracker.getProviders().isEmpty());
        verifyEventPublished();
        verify(bundleContext).ungetService(reference2);
    }

    @Test
    public void testDoesntTrackInvalidReferences() throws Exception {
        ServiceReference reference = mock(ServiceReference.class);
        Bundle bundle = mock(Bundle.class);
        BundleContext context = mock(BundleContext.class);

        when(reference.getBundle()).thenReturn(bundle);
        when(bundle.getBundleContext()).thenReturn(context);
        when(context.getService(reference)).thenReturn(null);

        tracker.onPluginFrameworkStarted(mock(PluginFrameworkStartedEvent.class));
        assertNull(tracker.addingService(reference));
        verify(eventPublisher, never()).publish(any(EffectivePermissionsChangedEvent.class));
    }

    @Test
    public void testStopsTrackingPermissionProviderWhenPluginFrameworkShuttingDown() throws Exception {
        //noinspection unchecked
        ServiceReference<EffectivePermissionsProvider> reference = mock(ServiceReference.class);
        EffectivePermissionsProvider provider1 = mockProviderReference(reference);

        tracker.onPluginFrameworkStarted(mock(PluginFrameworkStartedEvent.class));
        tracker.addingService(reference);

        assertEquals(Collections.singleton(provider1), tracker.getProviders());
        verifyEventPublished();

        tracker.onPluginFrameworkShuttingDown(mock(PluginFrameworkShuttingDownEvent.class));

        assertTrue(tracker.getProviders().isEmpty());

        verifyEventPublished();
    }

    private void verifyEventPublished() {
        verify(eventPublisher).publish(any(EffectivePermissionsChangedEvent.class));
        reset(eventPublisher);
    }

    private EffectivePermissionsProvider mockProviderReference(ServiceReference<EffectivePermissionsProvider> reference) {
        EffectivePermissionsProvider provider = mock(EffectivePermissionsProvider.class);

        when(bundleContext.getService(reference)).thenReturn(provider);
        return provider;
    }
}