package org.zalando.opentracing.proxy.autoconfigure;

import io.opentracing.Tracer;
import org.apiguardian.api.API;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.zalando.opentracing.proxy.core.ProxyTracer;
import org.zalando.opentracing.proxy.intercept.name.Naming;
import org.zalando.opentracing.proxy.intercept.name.Rename;
import org.zalando.opentracing.proxy.plugin.AutoTagging;
import org.zalando.opentracing.proxy.plugin.LogCorrelation;
import org.zalando.opentracing.proxy.plugin.TagPropagation;
import org.zalando.opentracing.proxy.spi.Plugin;

import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
import static org.apiguardian.api.API.Status.INTERNAL;
import static org.apiguardian.api.API.Status.STABLE;

@API(status = STABLE)
@Configuration
@EnableConfigurationProperties(ProxyProperties.class)
@AutoConfigureAfter(name = "io.opentracing.contrib.spring.tracer.configuration.TracerAutoConfiguration")
@AutoConfigureBefore(name = "io.opentracing.contrib.spring.tracer.configuration.TracerRegisterAutoConfiguration")
public class OpenTracingProxyAutoConfiguration {

    @API(status = INTERNAL)
    @Bean
    @ConditionalOnMissingBean(AutoTagging.class)
    @ConditionalOnProperty(
            name = "opentracing.proxy.auto-tagging.enabled",
            havingValue = "true",
            matchIfMissing = true)
    public AutoTagging autoTagging(final ProxyProperties properties) {
        final List<String> keys = properties.getAutoTagging().getKeys();
        final Map<String, String> mapping = keys.stream()
                .collect(toMap(identity(), identity()));

        return new AutoTagging(mapping);
    }

    @API(status = INTERNAL)
    @Bean
    @ConditionalOnMissingBean(LogCorrelation.class)
    @ConditionalOnProperty(
            name = "opentracing.proxy.log-correlation.enabled",
            havingValue = "true",
            matchIfMissing = true)
    public LogCorrelation logCorrelation(final ProxyProperties properties) {
        return new LogCorrelation()
                .withTraceId(properties.getLogCorrelation().getTraceId())
                .withSpanId(properties.getLogCorrelation().getSpanId())
                .withBaggage(properties.getLogCorrelation().getBaggage());
    }

    @API(status = INTERNAL)
    @Bean
    @ConditionalOnMissingBean(Naming.class)
    @ConditionalOnProperty(
            name = "opentracing.proxy.rename.enabled",
            havingValue = "true",
            matchIfMissing = true)
    public Rename rename(final ProxyProperties properties) {
        return new Rename(properties.getRename().getFormat());
    }

    @API(status = INTERNAL)
    @Bean
    @ConditionalOnMissingBean(TagPropagation.class)
    @ConditionalOnProperty(
            name = "opentracing.proxy.tag-propagation.enabled",
            havingValue = "true",
            matchIfMissing = true)
    public TagPropagation tagPropagation(final ProxyProperties properties) {
        return new TagPropagation(properties.getTagPropagation().getKeys());
    }

    @API(status = INTERNAL)
    @Bean
    @Primary
    @ConditionalOnSingleCandidate(Tracer.class)
    public Tracer proxyTracer(
            final Tracer tracer, final List<Plugin> plugins) {

        return reduce(plugins, new ProxyTracer(tracer), ProxyTracer::with);
    }

    private static <E, R> R reduce(
            final Iterable<E> elements,
            final R identity,
            final BiFunction<R, E, R> accumulator) {

        R result = identity;

        for (final E element : elements) {
            result = accumulator.apply(result, element);
        }

        return result;
    }

}