package ru.vyarus.dropwizard.guice.test;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceFilter;
import io.dropwizard.Application;
import io.dropwizard.Configuration;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.dropwizard.testing.ConfigOverride;
import net.sourceforge.argparse4j.inf.Namespace;
import org.junit.rules.ExternalResource;
import ru.vyarus.dropwizard.guice.injector.lookup.InjectorLookup;

import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.Enumeration;

/**
 * A JUnit rule for starting and stopping your guice application at the start and end of a test class.
 * <p>
 * By default, the {@link Application} will be constructed using reflection to invoke the nullary
 * constructor. If your application does not provide a public nullary constructor, you will need to
 * override the {@link #newApplication()} method to provide your application instance(s).</p>
 * <p>Based on {@link io.dropwizard.testing.junit.DropwizardAppRule}, but doesn't start jetty and as a consequence
 * jersey and guice web modules not initialized. Emulates managed objects lifecycle.</p>
 * <p>Suppose to be used for testing internal services business logic as lightweight alternative for
 * dropwizard rule.</p>
 *
 * @param <C> configuration type
 * @author Vyacheslav Rusakov
 * @since 23.10.2014
 */
public class GuiceyAppRule<C extends Configuration> extends ExternalResource {

    private final Class<? extends Application<C>> applicationClass;
    private final String configPath;

    private C configuration;
    private Application<C> application;
    private Environment environment;
    private TestCommand<C> command;

    public GuiceyAppRule(final Class<? extends Application<C>> applicationClass,
                         @Nullable final String configPath,
                         final ConfigOverride... configOverrides) {
        this.applicationClass = applicationClass;
        this.configPath = configPath;
        for (ConfigOverride configOverride : configOverrides) {
            configOverride.addToSystemProperties();
        }
    }

    public C getConfiguration() {
        return configuration;
    }

    @SuppressWarnings("unchecked")
    public <A extends Application<C>> A getApplication() {
        return (A) application;
    }

    public Environment getEnvironment() {
        return environment;
    }

    public Injector getInjector() {
        return InjectorLookup.getInjector(application).get();
    }

    public <T> T getBean(final Class<T> type) {
        return getInjector().getInstance(type);
    }

    protected Application<C> newApplication() {
        try {
            return applicationClass.newInstance();
        } catch (Exception e) {
            throw new IllegalStateException("Failed to instantiate application", e);
        }
    }

    @Override
    protected void before() throws Throwable {
        startIfRequired();
    }

    @Override
    @SuppressWarnings("PMD.NullAssignment")
    protected void after() {
        resetConfigOverrides();
        resetGuiceFilter();
        command.stop();
        command = null;
    }

    private void startIfRequired() {
        if (command != null) {
            return;
        }

        try {
            application = newApplication();

            final Bootstrap<C> bootstrap = new Bootstrap<C>(application) {
                @Override
                public void run(final C configuration, final Environment environment) throws Exception {
                    GuiceyAppRule.this.configuration = configuration;
                    GuiceyAppRule.this.environment = environment;
                    super.run(configuration, environment);
                }
            };

            application.initialize(bootstrap);

            startCommand(bootstrap);

        } catch (Exception e) {
            throw new IllegalStateException("Failed to start test environment", e);
        }
    }

    private void startCommand(final Bootstrap<C> bootstrap) throws Exception {
        command = new TestCommand<>(application);

        final ImmutableMap.Builder<String, Object> file = ImmutableMap.builder();
        if (!Strings.isNullOrEmpty(configPath)) {
            file.put("file", configPath);
        }
        final Namespace namespace = new Namespace(file.build());

        command.run(bootstrap, namespace);
    }

    private void resetConfigOverrides() {
        for (final Enumeration<?> props = System.getProperties().propertyNames(); props.hasMoreElements();) {
            final String keyString = (String) props.nextElement();
            if (keyString.startsWith("dw.")) {
                System.clearProperty(keyString);
            }
        }
    }

    private void resetGuiceFilter() {
        try {
            // GuiceFilter is initialized even in lightweight guicey rule case
            // This produce invalid warnings in log (about double filter initialization)
            final Method reset = GuiceFilter.class.getDeclaredMethod("reset");
            reset.setAccessible(true);
            reset.invoke(null);
        } catch (Exception e) {
            throw new IllegalStateException("Failed to cleanup GuiceFilter instance", e);
        }
    }
}

@@ -160,54 +160,8 @@
 or;%0A
-import com.google.inject.servlet.GuiceFilter;%0A
 impo
@@ -186,24 +186,24 @@
 pplication;%0A
+
 import io.dr
@@ -544,41 +544,8 @@
 le;%0A
-import java.lang.reflect.Method;%0A
 impo
@@ -3110,36 +3110,8 @@
 ();%0A
-        resetGuiceFilter();%0A
     
@@ -4718,16 +4718,16 @@
 tring);%0A
+
         
@@ -4751,516 +4751,7 @@
    %7D
-%0A%0A    private void resetGuiceFilter() %7B%0A        try %7B%0A            // GuiceFilter is initialized even in lightweight guicey rule case%0A            // This produce invalid warnings in log (about double filter initialization)%0A            final Method reset = GuiceFilter.class.getDeclaredMethod(%22reset%22);%0A            reset.setAccessible(true);%0A            reset.invoke(null);%0A        %7D catch (Exception e) %7B%0A            throw new IllegalStateException(%22Failed to cleanup GuiceFilter instance%22, e);%0A        %7D%0A    %7D
 %0A%7D%0A

remove guice filter reset in guicey rule