package org.springframework.integration.aws.sns.core;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.web.HttpRequestHandler;
import org.springframework.web.bind.annotation.RequestMethod;

import com.amazonaws.services.sns.util.SignatureChecker;

public class HttpEndpoint implements HttpRequestHandler {

	private static final String SUBSCRIPTION_CONFIRMATION = "SubscriptionConfirmation";
	private static final String NOTIFICATION = "Notification";
	private static final String SNS_MESSAGE_TYPE = "x-amz-sns-message-type";

	private final Log log = LogFactory.getLog(HttpEndpoint.class);

	private boolean passThru;
	private volatile NotificationHandler notificationHandler;

	public HttpEndpoint(NotificationHandler notificationHandler) {
		this();
		this.notificationHandler = notificationHandler;
	}

	public HttpEndpoint() {
		super();
		this.passThru = false;
	}

	public void setNotificationHandler(NotificationHandler notificationHandler) {
		this.notificationHandler = notificationHandler;
	}

	public void setPassThru(boolean passThru) {
		this.passThru = passThru;
	}

	@Override
	public void handleRequest(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		boolean unprocessable = true;
		if (request.getMethod().equals(RequestMethod.POST.toString())) {

			if (SUBSCRIPTION_CONFIRMATION.equals(request
					.getHeader(SNS_MESSAGE_TYPE))) {

				unprocessable = false;
				handleSubscriptionConfirmation(request, response);

			} else if (NOTIFICATION.equals(request.getHeader(SNS_MESSAGE_TYPE))) {

				if (notificationHandler != null) {
					unprocessable = false;
					handleNotification(request, response);
				}
			}

		}
		if (unprocessable) {
			log.warn("Unprocessable request: "
					+ request.getRequestURL().toString());
			response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
		}
	}

	private void handleSubscriptionConfirmation(HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		try {
			String source = readBody(request);
			log.debug("Subscription confirmation:\n" + source);
			JSONObject confirmation = new JSONObject(source);

			if (validSignature(source, confirmation)) {
				URL subscribeUrl = new URL(
						confirmation.getString("SubscribeURL"));
				HttpURLConnection http = (HttpURLConnection) subscribeUrl
						.openConnection();
				http.setDoOutput(false);
				http.setDoInput(true);
				StringBuilder buffer = new StringBuilder();
				byte[] buf = new byte[4096];
				while ((http.getInputStream().read(buf)) >= 0) {
					buffer.append(new String(buf));
				}
				log.debug("SubscribeURL response:\n" + buffer.toString());
			}
			response.setStatus(HttpServletResponse.SC_OK);

		} catch (JSONException e) {
			throw new IOException(e.getMessage(), e);
		}
	}

	private void handleNotification(HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		try {

			String source = readBody(request);
			log.debug("Message received:\n" + source);
			JSONObject notification = new JSONObject(source);
			if (passThru || validSignature(source, notification)) {
				notificationHandler.onNotification(notification
						.getString("Message"));
			}
			response.setStatus(HttpServletResponse.SC_ACCEPTED);
		} catch (JSONException e) {
			throw new IOException(e.getMessage(), e);
		}
	}

	private String readBody(HttpServletRequest request) throws IOException {

		StringBuilder buffer = new StringBuilder(request.getContentLength());
		String line = null;
		BufferedReader reader = request.getReader();
		while ((line = reader.readLine()) != null) {
			buffer.append(line);
		}

		return buffer.toString();
	}

	private boolean validSignature(String source, JSONObject jsonObject)
			throws JSONException, IOException {

		PublicKey pubKey = fetchPubKey(jsonObject.getString("SigningCertURL"));
		SignatureChecker signatureChecker = new SignatureChecker();

		return signatureChecker.verifyMessageSignature(source, pubKey);
	}

	private PublicKey fetchPubKey(String signingCertURL) throws IOException {

		try {
			URL url = new URL(signingCertURL);
			InputStream inStream = url.openStream();
			CertificateFactory cf = CertificateFactory.getInstance("X.509");
			X509Certificate cert = (X509Certificate) cf
					.generateCertificate(inStream);
			inStream.close();

			return cert.getPublicKey();

		} catch (Exception e) {
			throw new IOException(e);
		}
	}

}