/*
 * Copyright 2016 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.stream.app.aws;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.springframework.integration.test.matcher.HeaderMatcher.hasHeader;
import static org.springframework.integration.test.matcher.PayloadMatcher.hasPayload;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.junit.After;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.stream.app.s3.sink.AmazonS3SinkConfiguration;
import org.springframework.cloud.stream.app.s3.source.AmazonS3SourceConfiguration;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.cloud.stream.test.binder.MessageCollector;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.integration.file.FileHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.DeleteObjectsRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;

/**
 * @author Artem Bilan
 */
public class SpringCloudStreamAwsApplicationsITCase {

	@ClassRule
	public static final AwsIntegrationTestStackRule TEST_STACK_RULE = new AwsIntegrationTestStackRule();

	@Rule
	public TemporaryFolder temporaryFolder = new TemporaryFolder();

	private ConfigurableApplicationContext applicationContext;

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

	@Test
	public void testS3Source() throws IOException, InterruptedException {
		String bucket = "TestBucket";
		String key = "foo";
		String content = "Spring Cloud Stream AWS S3 Source test";

		this.applicationContext = SpringApplication.run(S3SourceBootConfiguration.class,
				"--s3.remoteDir=" + bucket, "--file.consumer.mode=lines", "--file.consumer.with-markers=false");

		ResourceIdResolver resourceIdResolver = this.applicationContext.getBean(ResourceIdResolver.class);

		AmazonS3 amazonS3 = this.applicationContext.getBean(AmazonS3.class);
		ObjectMetadata objectMetadata = new ObjectMetadata();
		objectMetadata.setContentLength(content.length());
		String bucketName = resourceIdResolver.resolveToPhysicalResourceId(bucket);
		amazonS3.putObject(bucketName, key, new ByteArrayInputStream(content.getBytes("UTF-8")), objectMetadata);

		try {
			Source source = this.applicationContext.getBean(Source.class);
			MessageCollector messageCollector = this.applicationContext.getBean(MessageCollector.class);
			Message<?> received = messageCollector.forChannel(source.output()).poll(10, TimeUnit.SECONDS);
			assertNotNull(received);
			assertThat(received, hasHeader(FileHeaders.FILENAME, key));
			assertThat(received, hasPayload("Spring Cloud Stream AWS S3 Source test"));
		}
		finally {
			amazonS3.deleteObject(bucketName, key);
		}
	}

	@Test
	public void testS3Sink() throws IOException, InterruptedException {
		String bucket = "TestBucket";
		String key = this.temporaryFolder.getRoot().getName();

		List<File> testFiles = new ArrayList<>();

		String content = "Spring Cloud Stream AWS S3 Sink test #";

		for (int i = 0; i < 2; i++) {
			File testFile = this.temporaryFolder.newFile();

			FileOutputStream fos = new FileOutputStream(testFile);
			fos.write((content + (i + 1)).getBytes());
			fos.close();

			testFiles.add(testFile);
		}

		this.applicationContext = SpringApplication.run(S3SinkBootConfiguration.class, "--s3.bucket=" + bucket);

		ResourceIdResolver resourceIdResolver = this.applicationContext.getBean(ResourceIdResolver.class);

		AmazonS3 amazonS3 = this.applicationContext.getBean(AmazonS3.class);
		String bucketName = resourceIdResolver.resolveToPhysicalResourceId(bucket);
		Sink sink = this.applicationContext.getBean(Sink.class);

		List<S3ObjectSummary> objectSummaries = null;
		try {
			sink.input().send(new GenericMessage<>(this.temporaryFolder.getRoot()));

			ObjectListing objectListing = amazonS3.listObjects(bucketName, key);
			objectSummaries = objectListing.getObjectSummaries();
			assertThat(objectSummaries.size(), equalTo(2));

			Collections.sort(objectSummaries, new Comparator<S3ObjectSummary>() {

				@Override
				public int compare(S3ObjectSummary o1, S3ObjectSummary o2) {
					return o1.getKey().compareTo(o2.getKey());
				}

			});

			Collections.sort(testFiles, new Comparator<File>() {

				@Override
				public int compare(File o1, File o2) {
					return o1.getName().compareTo(o2.getName());
				}

			});

			for (int i = 0; i < 2; i++) {
				S3ObjectSummary s3ObjectSummary = objectSummaries.get(i);
				File file = testFiles.get(i);
				String fileKey = key + "/" + file.getName();
				assertThat(s3ObjectSummary.getKey(), equalTo(fileKey));

				S3Object s3Object1 = amazonS3.getObject(bucketName, fileKey);
				assertThat(StreamUtils.copyToByteArray(s3Object1.getObjectContent()),
						equalTo(FileCopyUtils.copyToByteArray(file)));
			}
		}
		finally {
			if (objectSummaries != null) {
				for (S3ObjectSummary objectSummary : objectSummaries) {
					amazonS3.deleteObject(bucketName, objectSummary.getKey());
				}
			}
		}
	}


	@Configuration
	@EnableAutoConfiguration
	@Import(AmazonS3SourceConfiguration.class)
	public static class S3SourceBootConfiguration {

	}

	@Configuration
	@EnableAutoConfiguration
	@Import(AmazonS3SinkConfiguration.class)
	public static class S3SinkBootConfiguration {

	}

}