/*
 * Copyright 2016-2020 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.netflix.eureka.server;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.LeaseInfo;
import com.netflix.discovery.shared.Application;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cloud.netflix.eureka.server.InstanceRegistryTests.TestApplication;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;

/**
 * @author Bartlomiej Slota
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = TestApplication.class,
		webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
		value = { "spring.application.name=eureka", "logging.level.org.springframework."
				+ "cloud.netflix.eureka.server.InstanceRegistry=DEBUG" })
public class InstanceRegistryTests {

	private static final String APP_NAME = "MY-APP-NAME";

	private static final String HOST_NAME = "my-host-name";

	private static final String INSTANCE_ID = "my-host-name:8008";

	private static final int PORT = 8008;

	@SpyBean(PeerAwareInstanceRegistry.class)
	private InstanceRegistry instanceRegistry;

	@Before
	public void setup() {
		this.testEvents.applicationEvents.clear();
	}

	@Autowired
	private TestEvents testEvents;

	@Test
	public void testRegister() throws Exception {
		// creating instance info
		final LeaseInfo leaseInfo = getLeaseInfo();
		final InstanceInfo instanceInfo = getInstanceInfo(APP_NAME, HOST_NAME,
				INSTANCE_ID, PORT, leaseInfo);
		// calling tested method
		instanceRegistry.register(instanceInfo, false);
		// event of proper type is registered
		assertThat(this.testEvents.applicationEvents.size()).isEqualTo(1);
		assertThat(this.testEvents.applicationEvents
				.get(0) instanceof EurekaInstanceRegisteredEvent).isTrue();
		// event details are correct
		final EurekaInstanceRegisteredEvent registeredEvent = (EurekaInstanceRegisteredEvent) (this.testEvents.applicationEvents
				.get(0));
		assertThat(registeredEvent.getInstanceInfo()).isEqualTo(instanceInfo);
		assertThat(registeredEvent.getLeaseDuration())
				.isEqualTo(leaseInfo.getDurationInSecs());
		assertThat(registeredEvent.getSource()).isEqualTo(instanceRegistry);
		assertThat(registeredEvent.isReplication()).isFalse();
	}

	@Test
	public void testDefaultLeaseDurationRegisterEvent() throws Exception {
		// creating instance info
		final InstanceInfo instanceInfo = getInstanceInfo(APP_NAME, HOST_NAME,
				INSTANCE_ID, PORT, null);
		// calling tested method
		instanceRegistry.register(instanceInfo, false);
		// instance info duration is set to default
		final EurekaInstanceRegisteredEvent registeredEvent = (EurekaInstanceRegisteredEvent) (this.testEvents.applicationEvents
				.get(0));
		assertThat(registeredEvent.getLeaseDuration())
				.isEqualTo(LeaseInfo.DEFAULT_LEASE_DURATION);
	}

	@Test
	public void testInternalCancel() throws Exception {
		// calling tested method
		instanceRegistry.internalCancel(APP_NAME, HOST_NAME, false);
		// event of proper type is registered
		assertThat(this.testEvents.applicationEvents.size()).isEqualTo(1);
		assertThat(this.testEvents.applicationEvents
				.get(0) instanceof EurekaInstanceCanceledEvent).isTrue();
		// event details are correct
		final EurekaInstanceCanceledEvent registeredEvent = (EurekaInstanceCanceledEvent) (this.testEvents.applicationEvents
				.get(0));
		assertThat(registeredEvent.getAppName()).isEqualTo(APP_NAME);
		assertThat(registeredEvent.getServerId()).isEqualTo(HOST_NAME);
		assertThat(registeredEvent.getSource()).isEqualTo(instanceRegistry);
		assertThat(registeredEvent.isReplication()).isFalse();
	}

	@Test
	public void testRenew() throws Exception {
		// Creating two instances of the app
		final InstanceInfo instanceInfo1 = getInstanceInfo(APP_NAME, HOST_NAME,
				INSTANCE_ID, PORT, null);
		final InstanceInfo instanceInfo2 = getInstanceInfo(APP_NAME, HOST_NAME,
				"my-host-name:8009", 8009, null);
		// creating application list with an app having two instances
		final Application application = new Application(APP_NAME,
				Arrays.asList(instanceInfo1, instanceInfo2));
		final List<Application> applications = new ArrayList<>();
		applications.add(application);
		// stubbing applications list
		doReturn(applications).when(instanceRegistry).getSortedApplications();
		// calling tested method
		instanceRegistry.renew(APP_NAME, INSTANCE_ID, false);
		instanceRegistry.renew(APP_NAME, "my-host-name:8009", false);
		// event of proper type is registered
		assertThat(this.testEvents.applicationEvents.size()).isEqualTo(2);
		assertThat(this.testEvents.applicationEvents
				.get(0) instanceof EurekaInstanceRenewedEvent).isTrue();
		assertThat(this.testEvents.applicationEvents
				.get(1) instanceof EurekaInstanceRenewedEvent).isTrue();
		// event details are correct
		final EurekaInstanceRenewedEvent event1 = (EurekaInstanceRenewedEvent) (this.testEvents.applicationEvents
				.get(0));
		assertThat(event1.getAppName()).isEqualTo(APP_NAME);
		assertThat(event1.getServerId()).isEqualTo(INSTANCE_ID);
		assertThat(event1.getSource()).isEqualTo(instanceRegistry);
		assertThat(event1.getInstanceInfo()).isEqualTo(instanceInfo1);
		assertThat(event1.isReplication()).isFalse();

		final EurekaInstanceRenewedEvent event2 = (EurekaInstanceRenewedEvent) (this.testEvents.applicationEvents
				.get(1));
		assertThat(event2.getInstanceInfo()).isEqualTo(instanceInfo2);
	}

	private LeaseInfo getLeaseInfo() {
		LeaseInfo.Builder leaseBuilder = LeaseInfo.Builder.newBuilder();
		leaseBuilder.setRenewalIntervalInSecs(10);
		leaseBuilder.setDurationInSecs(15);
		return leaseBuilder.build();
	}

	private InstanceInfo getInstanceInfo(String appName, String hostName,
			String instanceId, int port, LeaseInfo leaseInfo) {
		InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();
		builder.setAppName(appName);
		builder.setHostName(hostName);
		builder.setInstanceId(instanceId);
		builder.setPort(port);
		builder.setLeaseInfo(leaseInfo);
		return builder.build();
	}

	@Configuration(proxyBeanMethods = false)
	@EnableAutoConfiguration
	@EnableEurekaServer
	protected static class TestApplication {

		@Bean
		public TestEvents testEvents() {
			return new TestEvents();
		}

	}

	protected static class TestEvents implements SmartApplicationListener {

		public final List<ApplicationEvent> applicationEvents = new LinkedList<>();

		@Override
		public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
			return EurekaInstanceRegisteredEvent.class.isAssignableFrom(eventType)
					|| EurekaInstanceCanceledEvent.class.isAssignableFrom(eventType)
					|| EurekaInstanceRenewedEvent.class.isAssignableFrom(eventType);
		}

		@Override
		public void onApplicationEvent(ApplicationEvent event) {
			this.applicationEvents.add(event);
		}

	}

}