package org.sagebionetworks.search;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Iterator;

import com.amazonaws.services.cloudsearchdomain.AmazonCloudSearchDomain;
import com.amazonaws.services.cloudsearchdomain.model.AmazonCloudSearchDomainException;
import com.amazonaws.services.cloudsearchdomain.model.DocumentServiceException;
import com.amazonaws.services.cloudsearchdomain.model.SearchException;
import com.amazonaws.services.cloudsearchdomain.model.SearchRequest;
import com.amazonaws.services.cloudsearchdomain.model.SearchResult;
import com.amazonaws.services.cloudsearchdomain.model.UploadDocumentsRequest;
import com.amazonaws.services.cloudsearchdomain.model.UploadDocumentsResult;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.sagebionetworks.repo.model.search.Document;
import org.sagebionetworks.util.ValidateArgument;

/**
 * A wrapper for AWS's AmazonCloudSearchDomain. DO NOT INSTANTIATE.
 * Use CloudSearchClientProvider to get an instance of this class.
 */
public class CloudsSearchDomainClientAdapter {
	static private Logger logger = LogManager.getLogger(CloudsSearchDomainClientAdapter.class);

	private AmazonCloudSearchDomain client;

	CloudsSearchDomainClientAdapter(AmazonCloudSearchDomain client){
		this.client = client;
	}

	public void sendDocuments(Iterator<Document> documents){
		ValidateArgument.required(documents, "documents");
		Iterator<Path> searchDocumentFileIterator = new CloudSearchDocumentFileIterator(documents);

		while(searchDocumentFileIterator.hasNext()) {
			Path file = searchDocumentFileIterator.next();

			try (InputStream fileStream = Files.newInputStream(file, StandardOpenOption.DELETE_ON_CLOSE)) {
				UploadDocumentsRequest request = new UploadDocumentsRequest()
						.withContentType("application/json")
						.withDocuments(fileStream)
						.withContentLength(Files.size(file));
				client.uploadDocuments(request);
			} catch (DocumentServiceException e) {
				throw handleCloudSearchExceptions(e);
			} catch (IOException e){
				throw new RuntimeException(e);
			}
		}
	}

	public void sendDocument(Document document){
		ValidateArgument.required(document, "batch");

		String documents = "[" + SearchUtil.convertSearchDocumentToJSONString(document) + "]";
		final byte[] documentBytes = documents.getBytes(StandardCharsets.UTF_8);
		UploadDocumentsRequest request = new UploadDocumentsRequest()
										.withContentType("application/json")
										.withDocuments(new ByteArrayInputStream(documentBytes))
										.withContentLength((long) documentBytes.length);
		try {
			UploadDocumentsResult result = client.uploadDocuments(request);
		} catch (DocumentServiceException e){
			int statusCode = e.getStatusCode();
			if(statusCode / 100 == 4) { //4xx status codes
				logger.error("The following search document was unable to be uploaded to CloudSearch:\n\n" +
				documents);
				throw new IllegalArgumentException(document.getId() + " search documents could not be uploaded.", e);
			}else {
				throw e;
			}

		}
	}

	SearchResult rawSearch(SearchRequest request) {
		ValidateArgument.required(request, "request");

		try{
			return client.search(request);
		}catch (SearchException e){
			throw handleCloudSearchExceptions(e);
		}
	}

	RuntimeException handleCloudSearchExceptions(AmazonCloudSearchDomainException e){
		int statusCode = e.getStatusCode();
		if(statusCode / 100 == 4){ //4xx status codes
			return new IllegalArgumentException(e);
		} else if (statusCode / 100 == 5){ // 5xx status codes
			// The AWS API already has retry logic for 5xx status codes so getting one here means retries failed
			// AmazonCloudSearchDomainException is a subclass of AmazonServiceException,
			// which is already handled by BaseController and mapped to a 502 HTTP error
			return e;
		}else {
			logger.warn("Failed for unexpected reasons with status: " + e.getStatusCode());
			return e;
		}
	}

}

@@ -1870,49 +1870,13 @@
 file
-, StandardOpenOption.DELETE_ON_CLOSE
 )) %7B%0A
+
 %09%09%09%09
@@ -2206,16 +2206,16 @@
 ion e)%7B%0A
-
 %09%09%09%09thro
@@ -2237,24 +2237,455 @@
 ception(e);%0A
+%09%09%09%7D finally %7B%0A%09%09%09%09// Here we perform manual delete instead of using StandardOpenOption.DELETE_ON_CLOSE in Files.newInputStream%0A%09%09%09%09// because in Linux, the file (if it is small enough) could get deleted before we call Files.size() to check its size.%0A%09%09%09%09// This is not reproducible on Windows systems.%0A%09%09%09%09try %7B%0A%09%09%09%09%09Files.delete(file);%0A%09%09%09%09%7D catch (IOException e) %7B%0A%09%09%09%09%09logger.error(%22Failed to delete temp file %22 + file);%0A%09%09%09%09%7D%0A
 %09%09%09%7D%0A%09%09%7D%0A%09%7D%0A

fix file not exist on Linux systems