/*
 * Copyright 2012-2017 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
 *
 *      http://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.boot.actuate.autoconfigure;

import java.util.Map;

import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.endpoint.MetricsEndpointMetricReader;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.export.MetricCopyExporter;
import org.springframework.boot.actuate.metrics.export.MetricExporters;
import org.springframework.boot.actuate.metrics.statsd.StatsdMetricWriter;
import org.springframework.boot.actuate.metrics.writer.GaugeWriter;
import org.springframework.boot.actuate.metrics.writer.MetricWriter;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.FixedSubscriberChannel;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.test.util.ReflectionTestUtils;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;

/**
 * Tests for {@link MetricExportAutoConfiguration}.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Simon Buettner
 */
public class MetricExportAutoConfigurationTests {

	@Rule
	public ExpectedException thrown = ExpectedException.none();

	private AnnotationConfigApplicationContext context;

	@After
	public void after() {
		if (this.context != null) {
			this.context.close();
		}
	}

	@Test
	public void metricsFlushAutomatically() throws Exception {
		this.context = new AnnotationConfigApplicationContext(WriterConfig.class,
				MetricRepositoryAutoConfiguration.class,
				MetricExportAutoConfiguration.class,
				PropertyPlaceholderAutoConfiguration.class);
		GaugeService gaugeService = this.context.getBean(GaugeService.class);
		assertThat(gaugeService).isNotNull();
		gaugeService.submit("foo", 2.7);
		MetricExporters flusher = this.context.getBean(MetricExporters.class);
		flusher.close(); // this will be called by Spring on shutdown
		MetricWriter writer = this.context.getBean("writer", MetricWriter.class);
		verify(writer, atLeastOnce()).set(any(Metric.class));
	}

	@Test
	public void defaultExporterWhenMessageChannelAvailable() throws Exception {
		this.context = new AnnotationConfigApplicationContext(
				MessageChannelConfiguration.class,
				MetricRepositoryAutoConfiguration.class,
				MetricsChannelAutoConfiguration.class,
				MetricExportAutoConfiguration.class,
				PropertyPlaceholderAutoConfiguration.class);
		MetricExporters exporter = this.context.getBean(MetricExporters.class);
		assertThat(exporter).isNotNull();
		assertThat(exporter.getExporters()).containsKey("messageChannelMetricWriter");
	}

	@Test
	public void provideAdditionalWriter() {
		this.context = new AnnotationConfigApplicationContext(WriterConfig.class,
				MetricRepositoryAutoConfiguration.class,
				MetricExportAutoConfiguration.class,
				PropertyPlaceholderAutoConfiguration.class);
		GaugeService gaugeService = this.context.getBean(GaugeService.class);
		assertThat(gaugeService).isNotNull();
		gaugeService.submit("foo", 2.7);
		MetricExporters exporters = this.context.getBean(MetricExporters.class);
		MetricCopyExporter exporter = (MetricCopyExporter) exporters.getExporters()
				.get("writer");
		exporter.setIgnoreTimestamps(true);
		exporter.export();
		MetricWriter writer = this.context.getBean("writer", MetricWriter.class);
		Mockito.verify(writer, Mockito.atLeastOnce()).set(any(Metric.class));
	}

	@Test
	public void exportMetricsEndpoint() {
		this.context = new AnnotationConfigApplicationContext(WriterConfig.class,
				MetricEndpointConfiguration.class, MetricExportAutoConfiguration.class,
				PropertyPlaceholderAutoConfiguration.class);
		MetricExporters exporters = this.context.getBean(MetricExporters.class);
		MetricCopyExporter exporter = (MetricCopyExporter) exporters.getExporters()
				.get("writer");
		exporter.setIgnoreTimestamps(true);
		exporter.export();
		MetricsEndpointMetricReader reader = this.context.getBean("endpointReader",
				MetricsEndpointMetricReader.class);
		Mockito.verify(reader, Mockito.atLeastOnce()).findAll();
	}

	@Test
	public void statsdMissingHost() throws Exception {
		this.context = new AnnotationConfigApplicationContext();
		this.context.register(WriterConfig.class, MetricEndpointConfiguration.class,
				MetricExportAutoConfiguration.class,
				PropertyPlaceholderAutoConfiguration.class);
		this.context.refresh();
		this.thrown.expect(NoSuchBeanDefinitionException.class);
		this.context.getBean(StatsdMetricWriter.class);
	}

	@SuppressWarnings("unchecked")
	@Test
	public void statsdWithHost() throws Exception {
		this.context = new AnnotationConfigApplicationContext();
		EnvironmentTestUtils.addEnvironment(this.context,
				"spring.metrics.export.statsd.host=localhost");
		this.context.register(MetricEndpointConfiguration.class,
				MetricExportAutoConfiguration.class,
				PropertyPlaceholderAutoConfiguration.class);
		this.context.refresh();
		StatsdMetricWriter statsdWriter = this.context.getBean(StatsdMetricWriter.class);
		assertThat(statsdWriter).isNotNull();
		SchedulingConfigurer schedulingConfigurer = this.context
				.getBean(SchedulingConfigurer.class);
		Map<String, GaugeWriter> exporters = (Map<String, GaugeWriter>) ReflectionTestUtils
				.getField(schedulingConfigurer, "writers");
		assertThat(exporters).containsValue(statsdWriter);

	}

	@Configuration
	public static class MessageChannelConfiguration {

		@Bean
		public SubscribableChannel metricsChannel() {
			return new FixedSubscriberChannel(new MessageHandler() {

				@Override
				public void handleMessage(Message<?> message) throws MessagingException {
				}

			});
		}

	}

	@Configuration
	public static class WriterConfig {

		@Bean
		@ExportMetricWriter
		public MetricWriter writer() {
			return Mockito.mock(MetricWriter.class);
		}

	}

	@Configuration
	public static class MetricEndpointConfiguration {

		@Bean
		@ExportMetricReader
		public MetricsEndpointMetricReader endpointReader() {
			return Mockito.mock(MetricsEndpointMetricReader.class);
		}

	}

}
