package com.capgemini.reports.common;

import com.capgemini.framework.common.MapHelper;
import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
import com.capgemini.framework.teamcenter.exceptions.NoTeamcenterConnectionException;
import com.capgemini.framework.teamcenter.exceptions.runtime.ConnectionLostException;
import com.capgemini.reports.exceptions.DuplicateReportEntryFound;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.exceptions.InvalidUserInputException;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.quartz.SchedulerException;

import javax.jms.JMSException;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/** Abstract class providing a framework for all servlets communicating with the application. **/
@Slf4j
public abstract class AbstractKurtServlet extends HttpServlet {
    private static final long serialVersionUID = 7233193093588056531L;

    private static final String CONTENT_TYPE_JSON = "application/json";

    /**
     * Receives user input, forwards to the actual implementation in . <br>
     * If the report could not be posted in the queue, then the resulting {@link JMSException} will be caught here, and a standardized failure message will be sent back to the server.
     * 
     * @param request the user request
     * @param response the response (a JSON object)
     * @throws ServletException
     * @throws IOException
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    @Override
    protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
        // Parse server request parameters
        processParams(request);

        JSONObject computedResponse;
        try (TcSession session = new TcSession()) {
            session.login();
            computedResponse = perform(request, session);
            response.setContentType(CONTENT_TYPE_JSON);
            response.getWriter().write(computedResponse.toString());
        } catch (DuplicateReportEntryFound e) {
            // The user is requesting a report with specific criteria, but a similar request has been made recently.
            log.debug("A Previous similar report execution request has been found, adding user to wishlist for this report.", e);
            response.setContentType(CONTENT_TYPE_JSON);
            response.getWriter().write(ServletMessageHelper.successMessage(e.getErrorMessageForUser()).toString());
        } catch (InvalidUserInputException e) {
            // A nice error message will be sent to the user to tell him what he did wrong
            log.error(e.getMessage(), e);
            response.setContentType(CONTENT_TYPE_JSON);
            response.getWriter().write(ServletMessageHelper.errorMessage(e).toString());
        } catch (NamingException e) {
            // Error due to miscommunication with the JMS queuing system
            log.error(ServletMessageHelper.POSTING_ERROR_MESSAGE_EN, e);
            response.getWriter().write(ServletMessageHelper.errorMessage(e).toString());
        } catch (JMSException e) {
            // Error due to miscommunication with the JMS queuing system
            log.error(ServletMessageHelper.POSTING_ERROR_MESSAGE_EN, e);
            response.getWriter().write(ServletMessageHelper.errorMessage(e).toString());
        } catch (SchedulerException e) {
            // Error due to miscommunication with the scheduler system
            log.error("Error caught while trying to delay the report.", e);
            response.getWriter().write(ServletMessageHelper.errorMessage(e).toString());
        } catch (NoTeamcenterConnectionException e) {
            // Error due to miscommunication with the Teamcenter servers
            log.error("Unable to connect to the Teamcenter server", e);
            response.getWriter().write(ServletMessageHelper.errorMessage(e).toString());
        } catch (ConnectionLostException e) {
            // Runtime exception, happens when connection is suddenly lost and not recovered despite attempts to do so
            log.error("Connection lost to Teamcenter server", e);
            response.getWriter().write(ServletMessageHelper.errorMessage(e).toString());
        }
    }

    /**
     * Forwarder of GET access to POST.
     * 
     * @param request the user request
     * @param response the response (a JSON object)
     * @throws ServletException
     * @throws IOException
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    @Override
    protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    /**
     * Parsing method for JSP request parameters.
     * 
     * @param aRequest
     */
    private void processParams(final HttpServletRequest aRequest) {
        if (!log.isDebugEnabled()) {
            return;
        }
        // Parse headers
        Map<String, String> theParsingMap = new HashMap<>();
        Enumeration<String> headerEnum = aRequest.getHeaderNames();
        while (headerEnum.hasMoreElements()) {
            String currentHeader = headerEnum.nextElement();
            Object attribute = aRequest.getAttribute(currentHeader);
            String currentValue = attribute == null ? "" : attribute.toString();
            theParsingMap.put(currentHeader, currentValue);
        }
        log.debug("Request headers: " + MapHelper.toString(theParsingMap));

        // Parse attributes
        theParsingMap = new HashMap<>();
        Enumeration<String> attNameEnum = aRequest.getAttributeNames();
        while (attNameEnum.hasMoreElements()) {
            String currentAtt = attNameEnum.nextElement();
            Object attribute = aRequest.getAttribute(currentAtt);
            String currentValue = attribute == null ? "" : attribute.toString();
            theParsingMap.put(currentAtt, currentValue);
        }
        log.debug("Request attributes: " + MapHelper.toString(theParsingMap));

        // Parse parameters
        theParsingMap = new HashMap<>();
        Map<String, String[]> parameterMap = aRequest.getParameterMap();
        for (Entry<String, String[]> entry : parameterMap.entrySet()) {
            String[] currentParamArray = entry.getValue();
            if (currentParamArray.length == 1) {
                theParsingMap.put(entry.getKey(), currentParamArray[0]);
            } else {
                for (int i = 0; i < currentParamArray.length; i++) {
                    theParsingMap.put(entry.getKey() + "[" + i + "]", currentParamArray[i]);
                }
            }
        }
        log.debug("Request parameters: " + MapHelper.toString(theParsingMap));
    }

    /**
     * This method will perform the job of the application. <br>
     * This method may throw a {@link InvalidInputException} with a nice failure message to the user.<br>
     * This method may throw any {@link KurtException} but, the user will receive a standardized failure message.
     * 
     * @param request the request
     * 
     * @return A {@link JSONObject} containing the response
     * 
     * @throws JMSException can be thrown if the job could not be posted into the queues.
     * @throws NamingException can be thrown when the required Queue could not be found
     * @throws InvalidInputException when the user gave bad inputs. The exceptions should contain a nice error message, which will be sent to the user.
     * @throws KurtException when some bug occurred. A generic error message will be sent to the user.
     */
    protected abstract JSONObject perform(final HttpServletRequest request, final TcSession session)
            throws JMSException, InvalidUserInputException, NamingException, SchedulerException;
}
