package com.atlassian.bitbucket.mesh.boot;

import com.atlassian.bitbucket.mesh.logback.LogbackUtils;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.logging.LoggingApplicationListener;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Stream;

/**
 * Applies levels defined as configuration properties, allowing the levels to be modified without requiring any
 * changes to the {@code logback-spring.xml} file.
 */
public class LogbackApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

    private static final String PREFIX_PROPERTY_GENERIC = "logging.logger";
    //This prefix is supported, even though Log4J is not used, because it's the logging system used by the
    //other products (Confluence, Jira), and some plugins (like UPM) use it for their integration tests
    private static final String PREFIX_PROPERTY_LOG4J = "log4j.logger";

    @Override
    public int getOrder() {
        return LoggingApplicationListener.DEFAULT_ORDER + 20; //Run after Spring Boot's listener
    }

    @Override
    public void onApplicationEvent(@Nonnull ApplicationEnvironmentPreparedEvent event) {
        Environment environment = event.getEnvironment();

        Stream.of(PREFIX_PROPERTY_GENERIC, PREFIX_PROPERTY_LOG4J)
                .map(prefix -> getSubProperties(prefix, environment))
                .forEach(levelsByLogger ->
                        levelsByLogger.forEach(LogbackUtils::setLevel));
    }

    private static Map<String, String> getSubProperties(String keyPrefix, Environment environment) {
        Binder binder = Binder.get(environment);
        return binder.bind(keyPrefix, Bindable.mapOf(String.class, String.class)).orElseGet(Collections::emptyMap);
    }
}
