/*
 * Copyright 2013-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.sleuth.instrument.web;

import brave.http.HttpRequest;
import brave.http.HttpRequestParser;
import brave.http.HttpResponseParser;
import brave.http.HttpTracing;
import brave.sampler.SamplerFunction;
import brave.sampler.SamplerFunctions;
import org.assertj.core.api.BDDAssertions;
import org.junit.jupiter.api.Test;

import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;

import static org.assertj.core.api.BDDAssertions.then;

public class TraceHttpAutoConfigurationTests {

	@Test
	public void defaultsClientSamplerToDefer() {
		contextRunner().run((context) -> {
			SamplerFunction<HttpRequest> clientSampler = context
					.getBean(HttpTracing.class).clientRequestSampler();

			then(clientSampler).isSameAs(SamplerFunctions.deferDecision());
		});
	}

	@Test
	public void configuresClientSkipPattern() throws Exception {
		contextRunner()
				.withPropertyValues("spring.sleuth.web.client.skip-pattern=foo.*|bar.*")
				.run((context) -> {
					SamplerFunction<HttpRequest> clientSampler = context
							.getBean(HttpTracing.class).clientRequestSampler();

					then(clientSampler).isInstanceOf(SkipPatternHttpClientSampler.class);
				});
	}

	@Test
	public void configuresUserProvidedHttpClientSampler() {
		contextRunner().withUserConfiguration(HttpClientSamplerConfig.class)
				.run((context) -> {
					SamplerFunction<HttpRequest> clientSampler = context
							.getBean(HttpTracing.class).clientRequestSampler();

					then(clientSampler).isSameAs(HttpClientSamplerConfig.INSTANCE);
				});
	}

	@Test
	public void defaultsServerSamplerToSkipPattern() {
		contextRunner().run((context) -> {
			SamplerFunction<HttpRequest> serverSampler = context
					.getBean(HttpTracing.class).serverRequestSampler();

			then(serverSampler).isInstanceOf(SkipPatternHttpServerSampler.class);
		});
	}

	@Test
	public void defaultsServerSamplerToDeferWhenSkipPatternCleared() {
		contextRunner().withPropertyValues("spring.sleuth.web.skip-pattern")
				.run((context) -> {
					SamplerFunction<HttpRequest> clientSampler = context
							.getBean(HttpTracing.class).serverRequestSampler();

					then(clientSampler).isSameAs(SamplerFunctions.deferDecision());
				});
	}

	@Test
	public void wrapsUserProvidedHttpServerSampler() {
		contextRunner().withUserConfiguration(HttpServerSamplerConfig.class)
				.run(thenCompositeHttpServerSamplerOf(HttpServerSamplerConfig.INSTANCE));
	}

	private ContextConsumer<AssertableApplicationContext> thenCompositeHttpServerSamplerOf(
			SamplerFunction<HttpRequest> instance) {
		return (context) -> {

			SamplerFunction<HttpRequest> serverSampler = context
					.getBean(HttpTracing.class).serverRequestSampler();

			then(serverSampler).isInstanceOf(CompositeHttpSampler.class);

			then(((CompositeHttpSampler) serverSampler).left)
					.isInstanceOf(SkipPatternHttpServerSampler.class);
			then(((CompositeHttpSampler) serverSampler).right).isSameAs(instance);
		};
	}

	@Test
	public void defaultHttpClientParser() {
		contextRunner().run((context) -> {
			HttpRequestParser clientRequestParser = context.getBean(HttpTracing.class)
					.clientRequestParser();
			HttpResponseParser clientResponseParser = context.getBean(HttpTracing.class)
					.clientResponseParser();

			then(clientRequestParser).isInstanceOf(HttpRequestParser.Default.class);
			then(clientResponseParser).isInstanceOf(HttpResponseParser.Default.class);
		});
	}

	@Test
	public void configuresUserProvidedHttpClientParser() {
		contextRunner().withUserConfiguration(HttpClientParserConfig.class)
				.run((context) -> {
					HttpRequestParser clientRequestParser = context
							.getBean(HttpTracing.class).clientRequestParser();
					HttpResponseParser clientResponseParser = context
							.getBean(HttpTracing.class).clientResponseParser();

					then(clientRequestParser)
							.isSameAs(HttpClientParserConfig.REQUEST_PARSER);
					then(clientResponseParser)
							.isSameAs(HttpClientParserConfig.RESPONSE_PARSER);
				});
	}

	@Test
	public void defaultHttpServerParser() {
		contextRunner().run((context) -> {
			HttpRequestParser serverRequestParser = context.getBean(HttpTracing.class)
					.serverRequestParser();
			HttpResponseParser serverResponseParser = context.getBean(HttpTracing.class)
					.serverResponseParser();

			then(serverRequestParser).isInstanceOf(HttpRequestParser.Default.class);
			then(serverResponseParser).isInstanceOf(HttpResponseParser.Default.class);
		});
	}

	@Test
	public void configuresUserProvidedHttpServerParser() {
		contextRunner().withUserConfiguration(HttpServerParserConfig.class)
				.run((context) -> {
					HttpRequestParser serverRequestParser = context
							.getBean(HttpTracing.class).serverRequestParser();
					HttpResponseParser serverResponseParser = context
							.getBean(HttpTracing.class).serverResponseParser();

					then(serverRequestParser)
							.isSameAs(HttpServerParserConfig.REQUEST_PARSER);
					then(serverResponseParser)
							.isSameAs(HttpServerParserConfig.RESPONSE_PARSER);
				});
	}

	/**
	 * Shows bean aliases work to configure the same instance for both client and server
	 */
	@Test
	public void configuresUserProvidedHttpClientAndServerParser() {
		contextRunner().withUserConfiguration(HttpParserConfig.class).run((context) -> {
			HttpRequestParser serverRequestParser = context.getBean(HttpTracing.class)
					.serverRequestParser();
			HttpResponseParser serverResponseParser = context.getBean(HttpTracing.class)
					.serverResponseParser();
			HttpRequestParser clientRequestParser = context.getBean(HttpTracing.class)
					.clientRequestParser();
			HttpResponseParser clientResponseParser = context.getBean(HttpTracing.class)
					.clientResponseParser();

			then(clientRequestParser).isSameAs(HttpParserConfig.REQUEST_PARSER);
			then(clientResponseParser).isSameAs(HttpParserConfig.RESPONSE_PARSER);
			then(serverRequestParser).isSameAs(HttpParserConfig.REQUEST_PARSER);
			then(serverResponseParser).isSameAs(HttpParserConfig.RESPONSE_PARSER);
		});
	}

	@Test
	public void hasNoCycles() {
		contextRunner()
				.withConfiguration(AutoConfigurations.of(SkipPatternConfiguration.class,
						TraceHttpAutoConfiguration.class))
				.withInitializer(c -> ((GenericApplicationContext) c)
						.setAllowCircularReferences(false))
				.run((context) -> {
					BDDAssertions.then(context.isRunning()).isEqualTo(true);
				});
	}

	private ApplicationContextRunner contextRunner(String... propertyValues) {
		return new ApplicationContextRunner().withPropertyValues(propertyValues)
				.withConfiguration(AutoConfigurations.of(TraceAutoConfiguration.class,
						TraceHttpAutoConfiguration.class,
						SkipPatternConfiguration.class));
	}

}

@Configuration
class HttpClientSamplerConfig {

	static final SamplerFunction<HttpRequest> INSTANCE = request -> null;

	@Bean(HttpClientSampler.NAME)
	SamplerFunction<HttpRequest> sleuthHttpClientSampler() {
		return INSTANCE;
	}

}

@Configuration
class HttpServerSamplerConfig {

	static final SamplerFunction<HttpRequest> INSTANCE = request -> null;

	@Bean(HttpServerSampler.NAME)
	SamplerFunction<HttpRequest> sleuthHttpServerSampler() {
		return INSTANCE;
	}

}

@Configuration
class HttpClientParserConfig {

	static final HttpRequestParser REQUEST_PARSER = (r, c, s) -> {
	};
	static final HttpResponseParser RESPONSE_PARSER = (r, c, s) -> {
	};

	@Bean(HttpClientRequestParser.NAME)
	HttpRequestParser sleuthHttpClientRequestParser() {
		return REQUEST_PARSER;
	}

	@Bean(HttpClientResponseParser.NAME)
	HttpResponseParser sleuthHttpClientResponseParser() {
		return RESPONSE_PARSER;
	}

}

@Configuration
class HttpServerParserConfig {

	static final HttpRequestParser REQUEST_PARSER = (r, c, s) -> {
	};
	static final HttpResponseParser RESPONSE_PARSER = (r, c, s) -> {
	};

	@Bean(HttpServerRequestParser.NAME)
	HttpRequestParser sleuthHttpServerRequestParser() {
		return REQUEST_PARSER;
	}

	@Bean(HttpServerResponseParser.NAME)
	HttpResponseParser sleuthHttpServerResponseParser() {
		return RESPONSE_PARSER;
	}

}

@Configuration
class HttpParserConfig {

	static final HttpRequestParser REQUEST_PARSER = (r, c, s) -> {
	};
	static final HttpResponseParser RESPONSE_PARSER = (r, c, s) -> {
	};

	@Bean(name = { HttpClientRequestParser.NAME, HttpServerRequestParser.NAME })
	HttpRequestParser sleuthHttpServerRequestParser() {
		return REQUEST_PARSER;
	}

	@Bean(name = { HttpClientResponseParser.NAME, HttpServerResponseParser.NAME })
	HttpResponseParser sleuthHttpServerResponseParser() {
		return RESPONSE_PARSER;
	}

}