/*
 * 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.aws.cache.config.annotation;

import java.lang.reflect.Field;
import java.util.Arrays;

import com.amazonaws.services.elasticache.AmazonElastiCache;
import com.amazonaws.services.elasticache.model.CacheCluster;
import com.amazonaws.services.elasticache.model.DescribeCacheClustersRequest;
import com.amazonaws.services.elasticache.model.DescribeCacheClustersResult;
import com.amazonaws.services.elasticache.model.Endpoint;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cloud.aws.cache.config.TestMemcacheServer;
import org.springframework.cloud.aws.cache.memcached.SimpleSpringMemcached;
import org.springframework.cloud.aws.core.env.stack.ListableStackResourceFactory;
import org.springframework.cloud.aws.core.env.stack.StackResource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.util.ReflectionUtils;

import static org.assertj.core.api.Assertions.assertThat;

@Deprecated
class ElastiCacheCachingConfigurationTest {

	private AnnotationConfigApplicationContext context;

	private static int getExpirationFromCache(Cache cache) throws IllegalAccessException {
		assertThat(cache instanceof SimpleSpringMemcached).isTrue();
		Field expiration = ReflectionUtils.findField(SimpleSpringMemcached.class,
				"expiration");
		assertThat(expiration).isNotNull();
		ReflectionUtils.makeAccessible(expiration);
		return expiration.getInt(cache);
	}

	@AfterEach
	void tearDown() throws Exception {
		if (this.context != null) {
			this.context.close();
		}
	}

	@Test
	void enableElasticache_configuredWithExplicitCluster_configuresExplicitlyConfiguredCaches()
			throws Exception {
		// Arrange

		// Act
		this.context = new AnnotationConfigApplicationContext(
				ApplicationConfigurationWithExplicitStackConfiguration.class);

		// Assert
		CacheManager cacheManager = this.context.getBean(CachingConfigurer.class)
				.cacheManager();
		assertThat(cacheManager.getCacheNames().size()).isEqualTo(2);
		Cache firstCache = cacheManager.getCache("firstCache");
		assertThat(firstCache.getName()).isNotNull();
		assertThat(getExpirationFromCache(firstCache)).isEqualTo(0);

		Cache secondCache = cacheManager.getCache("secondCache");
		assertThat(secondCache.getName()).isNotNull();
		assertThat(getExpirationFromCache(secondCache)).isEqualTo(0);
	}

	// @checkstyle:off
	@Test
	void enableElasticache_configuredWithExplicitClusterAndExpiration_configuresExplicitlyConfiguredCachesWithCustomExpirationTimes()
			// @checkstyle:on
			throws Exception {
		// Arrange

		// Act
		this.context = new AnnotationConfigApplicationContext(
				ApplicationConfigurationWithExplicitStackConfigurationAndExpiryTime.class);

		// Assert
		CacheManager cacheManager = this.context.getBean(CachingConfigurer.class)
				.cacheManager();
		assertThat(cacheManager.getCacheNames().size()).isEqualTo(2);
		Cache firstCache = cacheManager.getCache("firstCache");
		assertThat(firstCache.getName()).isNotNull();
		assertThat(getExpirationFromCache(firstCache)).isEqualTo(23);

		Cache secondCache = cacheManager.getCache("secondCache");
		assertThat(secondCache.getName()).isNotNull();
		assertThat(getExpirationFromCache(secondCache)).isEqualTo(42);
	}

	// @checkstyle:off
	@Test
	void enableElasticache_configuredWithExplicitClusterAndExpiration_configuresExplicitlyConfiguredCachesWithMixedExpirationTimes()
			throws Exception {
		// @checkstyle:on
		// Arrange

		// Act
		this.context = new AnnotationConfigApplicationContext(
				ApplicationConfigurationWithExplicitStackConfigurationAndMixedExpiryTime.class);

		// Assert
		CacheManager cacheManager = this.context.getBean(CachingConfigurer.class)
				.cacheManager();
		assertThat(cacheManager.getCacheNames().size()).isEqualTo(2);
		Cache firstCache = cacheManager.getCache("firstCache");
		assertThat(firstCache.getName()).isNotNull();
		assertThat(getExpirationFromCache(firstCache)).isEqualTo(12);

		Cache secondCache = cacheManager.getCache("secondCache");
		assertThat(secondCache.getName()).isNotNull();
		assertThat(getExpirationFromCache(secondCache)).isEqualTo(42);
	}

	@Test
	void enableElasticache_configuredWithoutExplicitCluster_configuresImplicitlyConfiguredCaches()
			throws Exception {
		// Arrange

		// Act
		this.context = new AnnotationConfigApplicationContext(
				ApplicationConfigurationWithNoExplicitStackConfiguration.class);

		// Assert
		CacheManager cacheManager = this.context.getBean(CachingConfigurer.class)
				.cacheManager();
		assertThat(cacheManager.getCacheNames().size()).isEqualTo(2);
		Cache firstCache = cacheManager.getCache("sampleCacheOneLogical");
		assertThat(firstCache.getName()).isNotNull();
		assertThat(getExpirationFromCache(firstCache)).isEqualTo(0);

		Cache secondCache = cacheManager.getCache("sampleCacheTwoLogical");
		assertThat(secondCache.getName()).isNotNull();
		assertThat(getExpirationFromCache(secondCache)).isEqualTo(0);
	}

	// @checkstyle:off
	@Test
	void enableElasticache_configuredWithoutExplicitClusterButDefaultExpiryTime_configuresImplicitlyConfiguredCachesWithDefaultExpiryTimeOnAllCaches()
			throws Exception {
		// @checkstyle:on
		// Arrange

		// Act
		this.context = new AnnotationConfigApplicationContext(
				ApplicationConfigurationWithNoExplicitStackConfigurationAndDefaultExpiration.class);

		// Assert
		CacheManager cacheManager = this.context.getBean(CachingConfigurer.class)
				.cacheManager();
		assertThat(cacheManager.getCacheNames().size()).isEqualTo(2);
		Cache firstCache = cacheManager.getCache("sampleCacheOneLogical");
		assertThat(firstCache.getName()).isNotNull();
		assertThat(getExpirationFromCache(firstCache)).isEqualTo(23);

		Cache secondCache = cacheManager.getCache("sampleCacheTwoLogical");
		assertThat(secondCache.getName()).isNotNull();
		assertThat(getExpirationFromCache(secondCache)).isEqualTo(23);
	}

	@EnableElastiCache({ @CacheClusterConfig(name = "firstCache"),
			@CacheClusterConfig(name = "secondCache") })
	static class ApplicationConfigurationWithExplicitStackConfiguration {

		@Bean
		AmazonElastiCache amazonElastiCache() {
			AmazonElastiCache amazonElastiCache = Mockito.mock(AmazonElastiCache.class);
			int port = TestMemcacheServer.startServer();
			DescribeCacheClustersRequest describeCacheClustersRequest = new DescribeCacheClustersRequest()
					.withCacheClusterId("firstCache");
			describeCacheClustersRequest.setShowCacheNodeInfo(true);
			Mockito.when(
					amazonElastiCache.describeCacheClusters(describeCacheClustersRequest))
					.thenReturn(
							new DescribeCacheClustersResult()
									.withCacheClusters(
											new CacheCluster()
													.withConfigurationEndpoint(
															new Endpoint()
																	.withAddress(
																			"localhost")
																	.withPort(port))
													.withEngine("memcached")));
			DescribeCacheClustersRequest secondCache = new DescribeCacheClustersRequest()
					.withCacheClusterId("secondCache");
			secondCache.setShowCacheNodeInfo(true);
			Mockito.when(amazonElastiCache.describeCacheClusters(secondCache)).thenReturn(
					new DescribeCacheClustersResult().withCacheClusters(new CacheCluster()
							.withConfigurationEndpoint(new Endpoint()
									.withAddress("localhost").withPort(port))
							.withEngine("memcached")));
			return amazonElastiCache;
		}

	}

	@EnableElastiCache({ @CacheClusterConfig(name = "firstCache", expiration = 23),
			@CacheClusterConfig(name = "secondCache", expiration = 42) })
	static class ApplicationConfigurationWithExplicitStackConfigurationAndExpiryTime {

		@Bean
		AmazonElastiCache amazonElastiCache() {
			AmazonElastiCache amazonElastiCache = Mockito.mock(AmazonElastiCache.class);
			int port = TestMemcacheServer.startServer();
			DescribeCacheClustersRequest firstCache = new DescribeCacheClustersRequest()
					.withCacheClusterId("firstCache");
			firstCache.setShowCacheNodeInfo(true);

			Mockito.when(amazonElastiCache.describeCacheClusters(firstCache)).thenReturn(
					new DescribeCacheClustersResult().withCacheClusters(new CacheCluster()
							.withConfigurationEndpoint(new Endpoint()
									.withAddress("localhost").withPort(port))
							.withEngine("memcached")));
			DescribeCacheClustersRequest secondCache = new DescribeCacheClustersRequest()
					.withCacheClusterId("secondCache");
			secondCache.setShowCacheNodeInfo(true);

			Mockito.when(amazonElastiCache.describeCacheClusters(secondCache)).thenReturn(
					new DescribeCacheClustersResult().withCacheClusters(new CacheCluster()
							.withConfigurationEndpoint(new Endpoint()
									.withAddress("localhost").withPort(port))
							.withEngine("memcached")));
			return amazonElastiCache;
		}

	}

	@EnableElastiCache(
			value = { @CacheClusterConfig(name = "firstCache"),
					@CacheClusterConfig(name = "secondCache", expiration = 42) },
			defaultExpiration = 12)
	static class ApplicationConfigurationWithExplicitStackConfigurationAndMixedExpiryTime {

		@Bean
		AmazonElastiCache amazonElastiCache() {
			AmazonElastiCache amazonElastiCache = Mockito.mock(AmazonElastiCache.class);
			int port = TestMemcacheServer.startServer();
			DescribeCacheClustersRequest firstCache = new DescribeCacheClustersRequest()
					.withCacheClusterId("firstCache");
			firstCache.setShowCacheNodeInfo(true);

			Mockito.when(amazonElastiCache.describeCacheClusters(firstCache)).thenReturn(
					new DescribeCacheClustersResult().withCacheClusters(new CacheCluster()
							.withConfigurationEndpoint(new Endpoint()
									.withAddress("localhost").withPort(port))
							.withEngine("memcached")));
			DescribeCacheClustersRequest secondCache = new DescribeCacheClustersRequest()
					.withCacheClusterId("secondCache");
			secondCache.setShowCacheNodeInfo(true);

			Mockito.when(amazonElastiCache.describeCacheClusters(secondCache)).thenReturn(
					new DescribeCacheClustersResult().withCacheClusters(new CacheCluster()
							.withConfigurationEndpoint(new Endpoint()
									.withAddress("localhost").withPort(port))
							.withEngine("memcached")));
			return amazonElastiCache;
		}

	}

	@EnableElastiCache
	static class ApplicationConfigurationWithNoExplicitStackConfiguration {

		@Bean
		AmazonElastiCache amazonElastiCache() {
			AmazonElastiCache amazonElastiCache = Mockito.mock(AmazonElastiCache.class);
			int port = TestMemcacheServer.startServer();
			DescribeCacheClustersRequest sampleCacheOneLogical = new DescribeCacheClustersRequest()
					.withCacheClusterId("sampleCacheOneLogical");
			sampleCacheOneLogical.setShowCacheNodeInfo(Boolean.TRUE);

			Mockito.when(amazonElastiCache.describeCacheClusters(sampleCacheOneLogical))
					.thenReturn(
							new DescribeCacheClustersResult()
									.withCacheClusters(
											new CacheCluster()
													.withConfigurationEndpoint(
															new Endpoint()
																	.withAddress(
																			"localhost")
																	.withPort(port))
													.withEngine("memcached")));

			DescribeCacheClustersRequest sampleCacheTwoLogical = new DescribeCacheClustersRequest()
					.withCacheClusterId("sampleCacheTwoLogical");
			sampleCacheTwoLogical.setShowCacheNodeInfo(Boolean.TRUE);

			Mockito.when(amazonElastiCache.describeCacheClusters(sampleCacheTwoLogical))
					.thenReturn(
							new DescribeCacheClustersResult()
									.withCacheClusters(
											new CacheCluster()
													.withConfigurationEndpoint(
															new Endpoint()
																	.withAddress(
																			"localhost")
																	.withPort(port))
													.withEngine("memcached")));
			return amazonElastiCache;
		}

		@Bean
		ListableStackResourceFactory stackResourceFactory() {
			ListableStackResourceFactory resourceFactory = Mockito
					.mock(ListableStackResourceFactory.class);
			Mockito.when(
					resourceFactory.resourcesByType("AWS::ElastiCache::CacheCluster"))
					.thenReturn(Arrays.asList(
							new StackResource("sampleCacheOneLogical", "sampleCacheOne",
									"AWS::ElastiCache::CacheCluster"),
							new StackResource("sampleCacheTwoLogical", "sampleCacheTwo",
									"AWS::ElastiCache::CacheCluster")));
			return resourceFactory;
		}

	}

	@EnableElastiCache(defaultExpiration = 23)
	static class ApplicationConfigurationWithNoExplicitStackConfigurationAndDefaultExpiration {

		@Bean
		AmazonElastiCache amazonElastiCache() {
			AmazonElastiCache amazonElastiCache = Mockito.mock(AmazonElastiCache.class);
			int port = TestMemcacheServer.startServer();
			DescribeCacheClustersRequest sampleCacheOneLogical = new DescribeCacheClustersRequest()
					.withCacheClusterId("sampleCacheOneLogical");
			sampleCacheOneLogical.setShowCacheNodeInfo(Boolean.TRUE);

			Mockito.when(amazonElastiCache.describeCacheClusters(sampleCacheOneLogical))
					.thenReturn(
							new DescribeCacheClustersResult()
									.withCacheClusters(
											new CacheCluster()
													.withConfigurationEndpoint(
															new Endpoint()
																	.withAddress(
																			"localhost")
																	.withPort(port))
													.withEngine("memcached")));

			DescribeCacheClustersRequest sampleCacheTwoLogical = new DescribeCacheClustersRequest()
					.withCacheClusterId("sampleCacheTwoLogical");
			sampleCacheTwoLogical.setShowCacheNodeInfo(Boolean.TRUE);

			Mockito.when(amazonElastiCache.describeCacheClusters(sampleCacheTwoLogical))
					.thenReturn(
							new DescribeCacheClustersResult()
									.withCacheClusters(
											new CacheCluster()
													.withConfigurationEndpoint(
															new Endpoint()
																	.withAddress(
																			"localhost")
																	.withPort(port))
													.withEngine("memcached")));
			return amazonElastiCache;
		}

		@Bean
		ListableStackResourceFactory stackResourceFactory() {
			ListableStackResourceFactory resourceFactory = Mockito
					.mock(ListableStackResourceFactory.class);
			Mockito.when(
					resourceFactory.resourcesByType("AWS::ElastiCache::CacheCluster"))
					.thenReturn(Arrays.asList(
							new StackResource("sampleCacheOneLogical", "sampleCacheOne",
									"AWS::ElastiCache::CacheCluster"),
							new StackResource("sampleCacheTwoLogical", "sampleCacheTwo",
									"AWS::ElastiCache::CacheCluster")));
			return resourceFactory;
		}

	}

}