package c4c.odata;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.olingo.odata2.api.client.batch.BatchChangeSet;
import org.apache.olingo.odata2.api.client.batch.BatchChangeSetPart;
import org.apache.olingo.odata2.api.client.batch.BatchPart;
import org.apache.olingo.odata2.api.client.batch.BatchSingleResponse;
import org.apache.olingo.odata2.api.edm.Edm;
import org.apache.olingo.odata2.api.ep.EntityProvider;
import org.apache.olingo.odata2.api.ep.EntityProviderException;

import com.fasterxml.jackson.databind.ObjectMapper;

public class AccountAttachmentODataConsumer {

	public static final String HTTP_METHOD_PUT = "PUT";
	public static final String HTTP_METHOD_POST = "POST";
	public static final String HTTP_METHOD_GET = "GET";
	public static final String HTTP_METHOD_PATCH = "PATCH";
	private static final String HTTP_METHOD_DELETE = "DELETE";

	public static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type";
	public static final String HTTP_HEADER_ACCEPT = "Accept";

	public static final String APPLICATION_JSON = "application/json";
	public static final String APPLICATION_XML = "application/xml";
	public static final String APPLICATION_ATOM_XML = "application/atom+xml";
	public static final String METADATA = "$metadata";
	public static final String SEPARATOR = "/";
	public static final String AUTHORIZATION_HEADER = "Authorization";
	public static final String CSRF_TOKEN_HEADER = "X-CSRF-Token";
	public static final String CSRF_TOKEN_FETCH = "Fetch";
	public static final String PROPERTIES_FILE = "settings.properties";
	
	public static final String C4C_TENANT = "C4C_TENANT";

	private static final Logger logger = Logger
			.getLogger(AccountAttachmentODataConsumer.class.getName());

	private String boundary = "batch_" + UUID.randomUUID().toString();

	private HttpClient m_httpClient = null;
	private Edm m_edm = null;
	private String m_csrfToken = null;

	private static String username;
	private static String password;
	private static String ODataServiceURL;
	private static String attachmentsFolder;
	
	static {
		logger.setLevel(Level.SEVERE);
	}

	private Edm readEdm() throws EntityProviderException,
			IllegalStateException, IOException {

		// This is used for both setting the Edm and CSRF Token :)
		if (m_edm != null) {
			return m_edm;
		}

		String serviceUrl = new StringBuilder(getODataServiceURL())
				.append(SEPARATOR).append(METADATA).toString();

		logger.info("Metadata url => " + serviceUrl);

		final HttpGet get = new HttpGet(serviceUrl);
		get.setHeader(AUTHORIZATION_HEADER, getAuthorizationHeader());
		get.setHeader(CSRF_TOKEN_HEADER, CSRF_TOKEN_FETCH);

		HttpResponse response = getHttpClient().execute(get);
		if(response.getStatusLine().getStatusCode() != 200) {
			logger.severe(response.getStatusLine().toString());
		}

		m_csrfToken = response.getFirstHeader(CSRF_TOKEN_HEADER).getValue();
		logger.info("CSRF token => " + m_csrfToken);

		m_edm = EntityProvider.readMetadata(response.getEntity().getContent(),
				false);
		return m_edm;
	}

	public String getCsrfToken() {

		if (m_csrfToken != null) {
			return m_csrfToken;
		}

		// Force a server call to fetch the EDM again
		m_edm = null;
		try {
			readEdm();
		} catch (EntityProviderException | IllegalStateException | IOException e) {
			logger.severe(e.getMessage());
			e.printStackTrace();
		}

		return m_csrfToken;
	}

	private HttpResponse executeBatchCall(String serviceUrl, final String body)
			throws ClientProtocolException, IOException {
		final HttpPost post = new HttpPost(URI.create(serviceUrl + "/$batch"));
		post.setHeader("Content-Type", "multipart/mixed;boundary=" + boundary);
		post.setHeader(AUTHORIZATION_HEADER, getAuthorizationHeader());
		post.setHeader(CSRF_TOKEN_HEADER, getCsrfToken());

		HttpEntity entity = new StringEntity(body);

		post.setEntity(entity);

		String logText = "REQUEST HEADERS:\n";
		for (Header h : post.getAllHeaders()) {
			logText = logText + h.getName() + " : " + h.getValue() + "\n";
		}
		logger.info(logText);

		HttpResponse response = getHttpClient().execute(post);

		logger.info("$Batch Response statusCode => "
				+ response.getStatusLine().getStatusCode());

		return response;
	}

	private HttpClient getHttpClient() {
		if (this.m_httpClient == null) {
			this.m_httpClient = HttpClientBuilder.create().build();
		}
		return this.m_httpClient;
	}

	public Optional<String> createAttachment(Attachment attachment)
			throws EntityProviderException, IOException, Exception {

		List<BatchPart> batchParts = new ArrayList<BatchPart>();

		BatchChangeSet changeSet = BatchChangeSet.newBuilder().build();
		String contentId = UUID.randomUUID().toString();

		Map<String, String> changeSetHeaders = new HashMap<String, String>();
		changeSetHeaders.put(HTTP_HEADER_CONTENT_TYPE, "application/json");
		changeSetHeaders.put("Content-ID", contentId);
		changeSetHeaders.put("Accept", APPLICATION_JSON);

		String uriAttachment = new StringBuilder(
				"AccountAttachmentCollection").toString();

		BatchChangeSetPart changeRequestAttachment = BatchChangeSetPart
				.method(HTTP_METHOD_POST).uri(uriAttachment)
				.body(serializeAttachment(attachment))
				.headers(changeSetHeaders).contentId(contentId).build();

		changeSet.add(changeRequestAttachment);

		batchParts.add(changeSet);

		InputStream body = EntityProvider.writeBatchRequest(batchParts,
				boundary);

		String payload = IOUtils.toString(body);

		// logger.info("$batch request : ");
		// logger.info(payload);

		String serviceUrl = getODataServiceURL();

		HttpResponse batchResponse = executeBatchCall(serviceUrl, payload);

		String logText = "RESPONSE HEADERS:\n";
		for (Header h : batchResponse.getAllHeaders()) {
			logText = logText + h.getName() + " : " + h.getValue() + "\n";
		}
		logger.info(logText);

		InputStream responseBody = batchResponse.getEntity().getContent();
		String contentType = batchResponse.getFirstHeader(
				HttpHeaders.CONTENT_TYPE).getValue();

		String response = IOUtils.toString(responseBody);

		// logger.info("$batch response :");
		// logger.info(response);

		// Process the batch response to get the ticket key
		List<BatchSingleResponse> responses = EntityProvider
				.parseBatchResponse(IOUtils.toInputStream(response),
						contentType);
		for (BatchSingleResponse rsp : responses) {
			// Look for only failed entries
			logger.info("Single Response status code => " + rsp.getStatusCode());
			if (Integer.parseInt(rsp.getStatusCode()) != 201) { // 201 -
																// HttpStatus
																// code created
				String retText = "Failed to create attachment for ["
						+ "ParentObjectID: " + attachment.get_ParentObjectID()
						+ ", Attachment name : " + attachment.get_Name()
						+ ", MimeType: " + attachment.get_MimeType()
						+ ", TypeCode: " + attachment.get_TypeCode()
						+ ", Filename: " + attachment.getFileName() + "] "
						+ "Error message: " + rsp.getBody();

				return Optional.of(retText);
			}
		}

		return Optional.empty();
	}

	public String serializeAttachment(Attachment a)
			throws EntityProviderException, IOException, Exception {

		// Input fields CorporateAccountExternalKey, TypeCode, Name, MimeType &
		// Binary
		Map<String, Object> prop = new HashMap<String, Object>();

		if (!StringUtils.isBlank(a.get_Name())) {
			prop.put("Name", a.get_Name());
		}

		if (!StringUtils.isBlank(a.get_ParentObjectID())) {
			prop.put("ParentObjectID", a.get_ParentObjectID());
		}

		if (!StringUtils.isBlank(a.get_TypeCode())) {
			prop.put("TypeCode", a.get_TypeCode());
		}

		if (!StringUtils.isBlank(a.get_MimeType())) {
			prop.put("MimeType", a.get_MimeType());
		}

		if (a.get_Binary() != null) {
			prop.put("Binary", a.get_Binary());
		}

		ObjectMapper mapper = new ObjectMapper();

		return mapper.writeValueAsString(prop);
	}

	private String getAuthorizationHeader() {
		// Note: This example uses Basic Authentication
		// Preferred option is to use OAuth SAML bearer flow.
		String temp = new StringBuilder(getUsername()).append(":")
				.append(getPassword()).toString();
		String result = "Basic "
				+ new String(Base64.encodeBase64(temp.getBytes()));
		logger.info("AuthorizationHeader " + result);
		return result;
	}

	public static Properties readSettings() throws IOException {
		Properties prop = new Properties();

		String path = PROPERTIES_FILE;
		
	    //load the file handle for main.properties
		FileInputStream is = new FileInputStream(path);
		
		if (is != null) {
			prop.load(is);

			// get the property value and print it out
			setODataServiceURL(prop.getProperty("ODATA_SERVICE_URL"));
			setUsername(prop.getProperty("USERNAME"));
			setAttachmentsFolder(prop.getProperty("ATTACHMENTS_FOLDER"));
		} else {
			throw new FileNotFoundException("Property file '" + path
					+ "' not found in classpath");
		}
		return prop;
	}

	public static String getUsername() {
		return username;
	}

	public static void setUsername(String username) {
		AccountAttachmentODataConsumer.username = username;
	}

	public static String getPassword() {
		return password;
	}

	public static void setPassword(String password) {
		AccountAttachmentODataConsumer.password = password;
	}

	public static String getODataServiceURL() {
		return ODataServiceURL;
	}

	public static void setODataServiceURL(String oDataServiceURL) {
		ODataServiceURL = oDataServiceURL;
	}

	public static String getAttachmentsFolder() {
		return attachmentsFolder;
	}

	public static void setAttachmentsFolder(String attachmentsFolder) {
		AccountAttachmentODataConsumer.attachmentsFolder = attachmentsFolder;
	}

}