package com.atlassian.stash.sal.api.lifecycle;

import com.atlassian.fugue.Pair;
import com.atlassian.plugin.Plugin;
import com.atlassian.plugin.PluginAccessor;
import com.atlassian.plugin.PluginController;
import com.atlassian.plugin.event.PluginEventManager;
import com.atlassian.plugin.event.events.PluginEnabledEvent;
import com.atlassian.sal.api.lifecycle.LifecycleAware;
import com.atlassian.sal.core.lifecycle.DefaultLifecycleManager;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;

import java.util.Hashtable;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class TestDefaultLifecycleManagerBase {
    protected final String pluginKey = "pluginKey";

    @Mock
    protected PluginEventManager pluginEventManager;
    @Mock
    protected PluginAccessor pluginAccessor;
    @Mock
    protected BundleContext bundleContext;

    @Mock
    protected Bundle pluginBundle;
    @Mock
    protected PluginController pluginController;
    protected ServiceListener serviceListener;

    protected boolean isApplicationSetup;
    protected int notifyOnStartCalled;
    protected PatchedLifecycleManager defaultLifecycleManager;

    @Before
    public void setUp() throws Exception {
        // Setup object under test

        isApplicationSetup = false;
        defaultLifecycleManager = new PatchedLifecycleManager(pluginEventManager, pluginAccessor, bundleContext) {
            @Override
            public boolean isApplicationSetUp() {
                return isApplicationSetup;
            }

            @Override
            protected void notifyOnStart() {
                super.notifyOnStart();
                ++notifyOnStartCalled;
            }
        };

        // Setup recurrent fixtures

        pluginBundle = mockBundle(pluginKey);
        // We need to use an answer here so we get serviceListener set during the call to facilitate race condition tests, and for
        // the same reason we can't use a vanilla ArgumentCaptor here (with polluting every serviceListener usage site).
        final Answer answer = new Answer() {
            @Override
            public Object answer(final InvocationOnMock invocation) throws Throwable {
                serviceListener = (ServiceListener) invocation.getArguments()[0];
                return null;
            }
        };
        doAnswer(answer).when(bundleContext).addServiceListener(any(ServiceListener.class), anyString());
    }

    protected void enablePlugin(final String pluginKey) {
        // Mark plugin enabled in accessor ...
        when(pluginAccessor.isPluginEnabled(pluginKey)).thenReturn(true);
        // ... and fire the event
        Plugin plugin = mock(Plugin.class);
        when(plugin.getKey()).thenReturn(pluginKey);
        defaultLifecycleManager.onPluginEnabled(new PluginEnabledEvent(plugin));
    }

    protected Bundle mockBundle(final String pluginKey) {
        final Bundle bundle = mock(Bundle.class);
        final ImmutableMap<String, String> headers = ImmutableMap.<String, String>builder()
                .put("Atlassian-Plugin-Key", pluginKey)
                .build();
        when(bundle.getHeaders()).thenReturn(new Hashtable<>(headers));
        return bundle;
    }

    protected Pair<LifecycleAware, ServiceReference<LifecycleAware>> mockLifecycleAwareAndRegisterService(final Bundle bundle, Class<LifecycleAware> classToMock) {
        final LifecycleAware lifecycleAware = mock(classToMock);
        ServiceReference<LifecycleAware> service = registerService(bundle, lifecycleAware);
        //noinspection unchecked
        return new Pair(lifecycleAware, service);
    }

    protected ServiceReference<LifecycleAware> registerService(final Bundle bundle, final LifecycleAware lifecycleAware) {
        @SuppressWarnings("unchecked")
        // Mocking generics requires the unchecked cast
        final ServiceReference<LifecycleAware> serviceReference = mock(ServiceReference.class);
        when(serviceReference.getBundle()).thenReturn(bundle);
        when(bundleContext.getService(serviceReference)).thenReturn(lifecycleAware);
        // Only send the ServiceEvent if we've been initialized, which is mean serviceListener has been set
        if (null != serviceListener) {
            serviceListener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, serviceReference));
        }
        return serviceReference;
    }

    protected void unregisterService(final ServiceReference serviceReference) {
        // Only send the ServiceEvent if we've been initialized, which is mean serviceListener has been set
        if (null != serviceListener) {
            serviceListener.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, serviceReference));
        }
        when(serviceReference.getBundle()).thenReturn(null);
    }
}
