package com.atlassian.bitbucket.internal.plugin.extender;

import org.eclipse.gemini.blueprint.extender.OsgiBeanFactoryPostProcessor;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import java.util.*;

import static java.util.Optional.ofNullable;

/**
 * An {@link OsgiBeanFactoryPostProcessor} that gets injected into the {@code org.eclipse.gemini.blueprint.extender}
 * bundle. This gemini bundle creates and configures the Spring application context for all OSGI bundles that bundle
 * one or more XML files in META-INF/spring. All Atlassian plugins fall in this category.
 * <p>
 * {@code PluginOsgiBeanFactoryPostProcessor} will receive a callback from gemini blueprints after it has created
 * a Spring application context and <em>before</em> any beans are instantiated. It will then register a
 * {@link BeanEventListenerScanner} in the newly created application context, which in turn will scan all beans that are
 * created for {@code @EventListener} and {@code PluginEventListener} annotations and automatically register them with
 * the {@link com.atlassian.event.api.EventPublisher}.
 * <p>
 * It is <em>necessary</em> to scan any beans before they are decorated with AOP proxies to ensure that automatic
 * registration in combination with manual registration does not result in the component receiving all events twice.
 *
 * @since 4.7
 */
public class PluginOsgiBeanFactoryPostProcessor implements OsgiBeanFactoryPostProcessor {

    /**
     * These are blacklisted because components in these plugins are known to register themselves with the
     * EventPublisher. We're skipping them to save time on startup. Functionally, nothing would go wrong if we
     * did try to auto-register them as the EventPublisher will only register a listener once.
     * <p>
     * Another reason for blacklisting is because the plugins may register themselves with the EventPublisher
     * <em>optionally</em> (e.g. only when a certain condition is met). Registering them anyway could break the
     * assumptions of the plugin and introduce problems.
     */
    private static final Set<String> BLACKLISTED_PLUGIN_KEYS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
            "com.atlassian.activeobjects.activeobjects-plugin",
            "com.atlassian.analytics.analytics-client",
            "com.atlassian.analytics.analytics-whitelist",
            "com.atlassian.applinks.applinks-basicauth-plugin",
            "com.atlassian.applinks.applinks-cors-plugin",
            "com.atlassian.applinks.applinks-oauth-plugin",
            "com.atlassian.applinks.applinks-plugin",
            "com.atlassian.applinks.applinks-trustedapps-plugin",
            "com.atlassian.auiplugin",
            "com.atlassian.bundles.json-schema-validator-atlassian-bundle",
            "com.atlassian.crowd.embedded.admin",
            "com.atlassian.healthcheck.atlassian-healthcheck",
            "com.atlassian.httpclient.atlassian-httpclient-plugin",
            "com.atlassian.integration.jira.jira-integration-plugin",
            "com.atlassian.jwt.jwt-plugin",
            "com.atlassian.markup.atlassian-markup-plugin",
            "com.atlassian.markup.atlassian-markup-renderer-plugin",
            "com.atlassian.oauth.admin",
            "com.atlassian.oauth.atlassian-oauth-consumer-spi",
            "com.atlassian.oauth.atlassian-oauth-service-provider-spi",
            "com.atlassian.oauth.consumer",
            "com.atlassian.oauth.consumer.sal",
            "com.atlassian.oauth.serviceprovider",
            "com.atlassian.oauth.serviceprovider.sal",
            "com.atlassian.plugins.atlassian-chaperone",
            "com.atlassian.plugins.atlassian-client-resource",
            "com.atlassian.plugins.atlassian-connect-plugin",
            "com.atlassian.plugins.atlassian-nav-links-plugin",
            "com.atlassian.plugins.atlassian-nps-plugin",
            "com.atlassian.plugins.atlassian-plugins-webresource-plugin",
            "com.atlassian.plugins.atlassian-plugins-webresource-rest",
            "com.atlassian.plugins.atlassian-remote-event-common-plugin",
            "com.atlassian.plugins.atlassian-remote-event-consumer-plugin",
            "com.atlassian.plugins.atlassian-remote-event-producer-plugin",
            "com.atlassian.plugins.base-hipchat-integration-plugin-api",
            "com.atlassian.plugins.base-hipchat-integration-plugin",
            "com.atlassian.plugins.issue-status-plugin",
            "com.atlassian.plugins.jquery",
            "com.atlassian.plugins.less-transformer-plugin",
            "com.atlassian.plugins.remote-link-aggregator-plugin",
            "com.atlassian.plugins.rest.atlassian-rest-module",
            "com.atlassian.plugins.shortcuts.atlassian-shortcuts-plugin",
            "com.atlassian.prettyurls.atlassian-pretty-urls-plugin",
            "com.atlassian.scala.plugins.scala-2.10-provider-plugin",
            "com.atlassian.scala.plugins.scala-2.11-provider-plugin",
            "com.atlassian.soy.soy-template-plugin",
            "com.atlassian.stash.plugins.stash-remote-event-bitbucket-server-spi",
            "com.atlassian.support.stp",
            "com.atlassian.templaterenderer.api",
            "com.atlassian.templaterenderer.atlassian-template-renderer-velocity1.6-plugin",
            "com.atlassian.upm.atlassian-universal-plugin-manager-plugin",
            "com.atlassian.webhooks.atlassian-webhooks-plugin"
            )));

    @Override
    public void postProcessBeanFactory(BundleContext bundleContext, ConfigurableListableBeanFactory beanFactory)
            throws BeansException, InvalidSyntaxException, BundleException {

        getPluginKey(bundleContext).ifPresent(pluginKey ->
                // only enhance plugins, skip plain OSGI bundles
                registerEventListenerRegistrar(pluginKey, bundleContext, beanFactory));
    }

    private static Optional<String> getPluginKey(BundleContext bundleContext) {
        return ofNullable(bundleContext.getBundle().getHeaders().get("Atlassian-Plugin-Key"));
    }

    private void registerEventListenerRegistrar(String pluginKey, BundleContext bundleContext,
                                                ConfigurableListableBeanFactory beanFactory) {
        if (!BLACKLISTED_PLUGIN_KEYS.contains(pluginKey)) {
            beanFactory.addBeanPostProcessor(new BeanEventListenerScanner(bundleContext));
        }
    }
}
