package com.sap.jam.webhooks.sample;

import java.io.IOException;
import java.io.Reader;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.client.ResponseProcessingException;
import javax.ws.rs.core.MediaType;

import org.apache.http.HttpStatus;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/**
 * This is a simple servlet demonstrating how webhooks calls sent by SAP Jam should be handled.
 */
@WebServlet("/")
public class WebhooksServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    private static final String JAM_BASE_URL = "https://developer.sapjam.com"; // Change this if your Jam instance is not in Developer
    private static final String JAM_OAUTH_TOKEN = "<YOUR JAM_OAUTH_TOKEN>";
    private static final String JAM_WEBHOOK_VERIFICATION_TOKEN = "<YOUR JAM_WEBHOOK_VERIFICATION_TOKEN>";
    
    private final Client httpClient = ClientBuilder.newClient();
    private final ExecutorService eventHandlingPool = Executors.newCachedThreadPool();

    /**
     * @see HttpServlet#HttpServlet()
     */
    public WebhooksServlet() {
        super();
    }

    /**
     * This is the POST end point that expects to receive webhook callbacks from Jam. Upon receiving any webhook callback,
     * clients must verify that the verification token within the request payload exists and is correct. After verification,
     * the client must echo the challenge string contained within the payload as the response to the request.
     * 
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    @Override
    protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Received POST call");

        final JSONObject callbackPayload = parseJSONRequest(request.getReader());

        final String verificationToken = (String)callbackPayload.get("@sapjam.hub.verificationToken");
        final String challengeString = (String)callbackPayload.get("@sapjam.hub.challenge");
        final JSONArray events = (JSONArray)callbackPayload.get("value");

        // Verify the identity of the server
        if (verificationToken.equals(JAM_WEBHOOK_VERIFICATION_TOKEN)) {
            handleEvents(events);
            response.getWriter().println(challengeString); // Echo the challenge string as a response
        } else {
            response.sendError(HttpStatus.SC_FORBIDDEN);
        }
    }

    /**
     * To make sure the webhook client responds to Jam webhook calls within a couple seconds, we encourage all event handling to be done
     * asynchronously. In Java, this can be done using with {@link ForkJoinPool} or {@link Executors} (used here).
     * 
     * @param events
     */
    private void handleEvents(final JSONArray events) {
    	if (events == null) {
    		return;
    	}
    	
        for (final Object event : events) {
            eventHandlingPool.execute(new Runnable() {

                @Override
                public void run() {
                    replyToEvent((JSONObject)event);
                }
            });
        }
    }

    /**
     * For this sample application, we want our webhooks client to post a reply to each document or comment that triggered a
     * webhook call. Since different types of event entities have different APIs for posting comments, we will have check the
     * type of the associated entity for each event to make the appropriate API call.
     * 
     * See our webhooks documentation for the possible event types and entity types that could be sent by a
     * webhook call.
     * 
     * @param eventObject
     */
    private void replyToEvent(final JSONObject eventObject) {
        final String eventEntityId = (String)eventObject.get("Id");
        final String entityType = (String)eventObject.get("@sapjam.event.entityType");

        System.out.printf("Processing event type: %s", entityType);

        switch (entityType) {
        case "ContentItem":
            final String contentItemType = (String)eventObject.get("ContentItemType");
            postToContentItem(eventEntityId, contentItemType);
            break;

        case "FeedEntry":
            postToFeed(eventEntityId);
            break;

        case "Comment":
            final String feedEntryId = (String)((JSONObject)eventObject.get("ParentFeedEntry")).get("Id");
            postToFeed(feedEntryId);
            break;

        default:
            break;
        }
    }

    /**
     * See @see <a href=
     * "https://developer.sapjam.com/ODataDocs/ui#!/Content/post_ContentItems_Id_Id_ContentItemType_ContentItemType_FeedEntries">
     * SAP Jam OData Documentation for reference</a>
     * 
     * @param contentItemId
     * @param contentItemType
     */
    private void postToContentItem(final String contentItemId, final String contentItemType) {
        final String oDataPath = String.format("ContentItems(Id='%s', ContentItemType='%s')/FeedEntries", contentItemId, contentItemType);
        postToOData(oDataPath, "{\"Text\": \"I've received a webhook call!\"}");
    }

    /**
     * See @see
     * <a href="https://developer.sapjam.com/ODataDocs/ui#!/Feed/get_FeedEntries_id_Replies">SAP Jam OData Documentation for
     * reference</a>
     * 
     * @param feedEntryId
     */
    private void postToFeed(final String feedEntryId) {
        final String oDataPath = String.format("FeedEntries('%s')/Replies", feedEntryId);
        postToOData(oDataPath, "{\"Text\": \"I've received a webhook call!\"}");
    }

    /**
     * This method encapsulates a basic way of making most POST calls to the Jam OData API under the JSON format.
     * 
     * @param oDataPath API end point to call
     * @param payload a JSON request body
     */
    private void postToOData(final String oDataPath, final String payload) {
        System.out.printf("Making Jam OData POST call to %s with payload: %n%s", oDataPath, payload);

        httpClient
            .target(JAM_BASE_URL)
            .path("/api/v1/OData/" + oDataPath)
            .queryParam("$format", "json")
            .request(MediaType.APPLICATION_JSON)
            .header("Authorization", "Bearer " + JAM_OAUTH_TOKEN)
            .header("Content-Type", MediaType.APPLICATION_JSON)
            .header("Accept", MediaType.APPLICATION_JSON)
            .async()
            .post(Entity.json(payload), new InvocationCallback<String>() {

                @Override
                public void completed(final String response) {
                    System.out.println("Received response: " + response);
                }

                @Override
                public void failed(final Throwable throwable) {
                    final ResponseProcessingException exception = (ResponseProcessingException)throwable;
                    final String responseString = exception.getResponse().readEntity(String.class);
                    System.out.println("Received error response: " + responseString);
                    throwable.printStackTrace();
                }
            });
    }

    /**
     * Converts a text input stream into a JSON object.
     * 
     * @param reader
     * @return a parsed JSONObject. If parsing fails, an empty JSON Object is returned.
     */
    private JSONObject parseJSONRequest(final Reader reader) {
        final JSONParser jsonParser = new JSONParser();
        JSONObject parsedPayload = new JSONObject();

        try {
            parsedPayload = (JSONObject)jsonParser.parse(reader);
        } catch (ParseException | IOException e) {
            e.printStackTrace();
        }

        return parsedPayload;
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    @Override
    protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().println("Please add this endpoint as the callback URL in a Push Notification Subscription on SAP Jam.");
    }

    @Override
    public void destroy() {
        httpClient.close();
        eventHandlingPool.shutdown();
    }
}