/*
 * 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.devtools.remote.client;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

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

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.devtools.autoconfigure.OptionalLiveReloadServer;
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher;
import org.springframework.boot.devtools.filewatch.ChangedFiles;
import org.springframework.boot.devtools.livereload.LiveReloadServer;
import org.springframework.boot.devtools.remote.client.RemoteClientConfiguration.LiveReloadConfiguration;
import org.springframework.boot.devtools.remote.server.Dispatcher;
import org.springframework.boot.devtools.remote.server.DispatcherFilter;
import org.springframework.boot.devtools.restart.MockRestarter;
import org.springframework.boot.devtools.restart.RestartScopeInitializer;
import org.springframework.boot.devtools.tunnel.client.TunnelClient;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.SocketUtils;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

/**
 * Tests for {@link RemoteClientConfiguration}.
 *
 * @author Phillip Webb
 */
public class RemoteClientConfigurationTests {

	@Rule
	public MockRestarter restarter = new MockRestarter();

	@Rule
	public OutputCapture output = new OutputCapture();

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

	private AnnotationConfigServletWebServerApplicationContext context;

	private static int remotePort = SocketUtils.findAvailableTcpPort();

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

	@Test
	public void warnIfDebugAndRestartDisabled() throws Exception {
		configure("spring.devtools.remote.debug.enabled:false",
				"spring.devtools.remote.restart.enabled:false");
		assertThat(this.output.toString())
				.contains("Remote restart and debug are both disabled");
	}

	@Test
	public void warnIfNotHttps() throws Exception {
		configure("http://localhost", true);
		assertThat(this.output.toString()).contains("is insecure");
	}

	@Test
	public void doesntWarnIfUsingHttps() throws Exception {
		configure("https://localhost", true);
		assertThat(this.output.toString()).doesNotContain("is insecure");
	}

	@Test
	public void failIfNoSecret() throws Exception {
		this.thrown.expect(BeanCreationException.class);
		this.thrown.expectMessage("required to secure your connection");
		configure("http://localhost", false);
	}

	@Test
	public void liveReloadOnClassPathChanged() throws Exception {
		configure();
		Set<ChangedFiles> changeSet = new HashSet<>();
		ClassPathChangedEvent event = new ClassPathChangedEvent(this, changeSet, false);
		this.context.publishEvent(event);
		LiveReloadConfiguration configuration = this.context
				.getBean(LiveReloadConfiguration.class);
		configuration.getExecutor().shutdown();
		configuration.getExecutor().awaitTermination(2, TimeUnit.SECONDS);
		LiveReloadServer server = this.context.getBean(LiveReloadServer.class);
		verify(server).triggerReload();
	}

	@Test
	public void liveReloadDisabled() throws Exception {
		configure("spring.devtools.livereload.enabled:false");
		this.thrown.expect(NoSuchBeanDefinitionException.class);
		this.context.getBean(OptionalLiveReloadServer.class);
	}

	@Test
	public void remoteRestartDisabled() throws Exception {
		configure("spring.devtools.remote.restart.enabled:false");
		this.thrown.expect(NoSuchBeanDefinitionException.class);
		this.context.getBean(ClassPathFileSystemWatcher.class);
	}

	@Test
	public void remoteDebugDisabled() throws Exception {
		configure("spring.devtools.remote.debug.enabled:false");
		this.thrown.expect(NoSuchBeanDefinitionException.class);
		this.context.getBean(TunnelClient.class);
	}

	private void configure(String... pairs) {
		configure("http://localhost", true, pairs);
	}

	private void configure(String remoteUrl, boolean setSecret, String... pairs) {
		this.context = new AnnotationConfigServletWebServerApplicationContext();
		new RestartScopeInitializer().initialize(this.context);
		this.context.register(Config.class, RemoteClientConfiguration.class);
		String remoteUrlProperty = "remoteUrl:" + remoteUrl + ":"
				+ RemoteClientConfigurationTests.remotePort;
		EnvironmentTestUtils.addEnvironment(this.context, remoteUrlProperty);
		EnvironmentTestUtils.addEnvironment(this.context, pairs);
		if (setSecret) {
			EnvironmentTestUtils.addEnvironment(this.context,
					"spring.devtools.remote.secret:secret");
		}
		this.context.refresh();
	}

	@Configuration
	static class Config {

		@Bean
		public TomcatServletWebServerFactory tomcat() {
			return new TomcatServletWebServerFactory(remotePort);
		}

		@Bean
		public LiveReloadServer liveReloadServer() {
			return mock(LiveReloadServer.class);
		}

		@Bean
		public DispatcherFilter dispatcherFilter() throws IOException {
			return new DispatcherFilter(dispatcher());
		}

		public Dispatcher dispatcher() throws IOException {
			Dispatcher dispatcher = mock(Dispatcher.class);
			ServerHttpRequest anyRequest = (ServerHttpRequest) any();
			ServerHttpResponse anyResponse = (ServerHttpResponse) any();
			given(dispatcher.handle(anyRequest, anyResponse)).willReturn(true);
			return dispatcher;
		}

	}

}
