package com.capgemini.reports.common.scheduling;

import com.capgemini.framework.common.ReflectionHelper;
import com.capgemini.framework.common.activedirectory.ActiveDirectory;
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.common.AbstractSubmitServlet;
import com.capgemini.reports.common.ServletMessageHelper;
import com.capgemini.reports.database.javabean.DbUser.UserData;
import com.capgemini.reports.database.javabean.enumeration.SchedulingEnum;
import com.capgemini.reports.exceptions.*;
import com.capgemini.reports.execution.AbstractExecutionReport;
import com.capgemini.reports.periodicity.PeriodFactory;
import com.capgemini.reports.periodicity.SchedulingPeriod;
import com.capgemini.reports.quartz.SchedulerServer;
import com.google.common.base.Joiner;
import com.teamcenter.schemas.soa._2006_03.exceptions.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.quartz.SchedulerException;

import javax.ejb.EJB;
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.text.ParseException;
import java.util.Map.Entry;

/** Abstract class which contains the framework for submitting and checking the necessary parameters to produce a scheduled report. **/
@Slf4j
public abstract class AbstractGenerateScheduledTemplateServlet extends HttpServlet {
    private static final long serialVersionUID = -1942251448070464203L;

    private static final String KEY_DATE = "datepicker_planification";
    private static final String KEY_PERIOD = "period";

    @EJB
    protected SchedulerServer schedulingServer;

    // Configuration for reflection
    private static final String CLASS_IMPL_PACKAGE = "com.capgemini.reports.";
    private static final String PREFIX_SUBMIT_SERVLET_CLASS = "SubmitServlet";
    private static final String CONTENT_TYPE_JSON = "application/json";
    private static final String INTERNAL_ERROR_MESSAGE_FR = UserInputErrorEnum.INTERNAL_ERROR.getMsgFr();

    // Keys to findAll data from the HttpServletRequest
    private static final String KEY_REPORT_CODE = "report_code";
    private static final String KEY_PLANIFICATION_NAME = "scheduling_name";
    static final String KEY_SCHEDULING_REPORT_ID = "scheduling_report_id";

    /**
     * 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);
    }

    @Override
    protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
        try (TcSession session = new TcSession()) {
            session.login();
            JSONObject computedResponse = perform(request, session);
            response.setContentType(CONTENT_TYPE_JSON);
            response.getWriter().write(computedResponse.toString());
        } catch (DuplicateSchedulingEntryFound e) {
            // Error due to miscommunication with the scheduler system
            log.error("A Scheduling with the same name already exists.", e);
            response.getWriter().write(ServletMessageHelper.errorMessage(e).toString());
        } catch (InvalidUserInputException e) {
            // Error due to miscommunication with the scheduler system
            log.error("The report parameters were invalid.", 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 schedule the report.", e);
            response.getWriter().write(ServletMessageHelper.errorMessage(e).toString());
        } catch (NotSupportedException e) {
            log.error("Unable to find the requested report definition.", e);
            response.getWriter().write(ServletMessageHelper.errorMessage(INTERNAL_ERROR_MESSAGE_FR).toString());
        } catch (FileLoadingException e) {
            log.error("Unable to read the policies file.", e);
            response.getWriter().write(ServletMessageHelper.errorMessage(INTERNAL_ERROR_MESSAGE_FR).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());
        } catch (ServiceException e) {
            String msg = "Servlet could not logout the Teamcenter connection Properly, some session might still be running...";
            log.error(msg, e);
        } catch (KurtException e) {
            log.error("Unable to perform the request.", e);
            response.getWriter().write(ServletMessageHelper.errorMessage(INTERNAL_ERROR_MESSAGE_FR).toString());
        }
    }

    /**
     * Perform a scheduling operation (new/update/delete).<br>
     * If the method is successful, returns a {@link JSONObject} success message. <br>
     * If the method fails in any way, an exception must be thrown.
     * 
     * @throws NotSupportedException If the report instance could not be created (not developped yet?)
     * @throws FileLoadingException If unable to load policies to perform the quick checks
     * @throws InvalidUserInputException If any report parameter or scheduling parameter could not be understood
     * @throws SchedulerException if a scheduling operation failed.
     * @throws NotFoundException if elements potentially affected by the request could not be found (DB etc.)
     * @throws InsufficientParametersException if not enough parameters or attribute were passed in the http request
     */
    protected abstract JSONObject perform(final HttpServletRequest request, final TcSession session)
            throws SchedulerException, NotSupportedException, InvalidUserInputException, FileLoadingException, NotFoundException,
            InsufficientParametersException, DuplicateSchedulingEntryFound;

    /** Obtains the name of the planification from the {@link HttpServletRequest}. */
    String getPlanificationName(final HttpServletRequest request) {
        return request.getParameter(KEY_PLANIFICATION_NAME);
    }

    /** Obtains dynamically an instance of ExecutionReport to match the {@link HttpServletRequest request} parameters. */
    AbstractExecutionReport<?, ?> getExecutionReportImpl(final HttpServletRequest request) throws NotSupportedException {
        String reportCode = request.getParameter(KEY_REPORT_CODE);
        if (reportCode == null) {
            String aMessage = "Unable find key [" + KEY_REPORT_CODE + "] in request parameter map: " + request.getParameterMap().toString();
            throw new NotSupportedException(aMessage);
        }
        // Recovery of the name of a not known class because of the package
        String className = getBuilderClassName(reportCode);

        // Research and recovery of the builder class using Reflection
        Object invokedObj = ReflectionHelper.newFromClassForName(className);

        if ((invokedObj == null) || (!(invokedObj instanceof AbstractSubmitServlet))) {
            String aMessage = "Unable to invoke the servlet: " + className + " of the report " + reportCode;
            throw new NotSupportedException(aMessage);
        } else {
            // Use the builder
            AbstractSubmitServlet<?> submitServlet = (AbstractSubmitServlet<?>) invokedObj;
            return submitServlet.prepareReport(request);
        }
    }

    /**
     * Get the name of the SubmitServlet class of the appropriate report thanks to his code.
     * 
     * @param reportCode String - the report code
     * @return theClassNameBuilder String
     */
    private String getBuilderClassName(final String reportCode) {
        return CLASS_IMPL_PACKAGE + reportCode.replace("R", "r") + "." + PREFIX_SUBMIT_SERVLET_CLASS + reportCode;
    }

    SchedulingPeriod getSchedulingData(final HttpServletRequest request) throws InvalidUserInputException {
        if (log.isTraceEnabled()) {
            StringBuilder result = new StringBuilder("Parsing http Request for scheduling periodicity:");
            for (Entry<String, String[]> paramEntry : request.getParameterMap().entrySet()) {
                result.append(paramEntry.getKey()).append("=");
                String paramValues = Joiner.on(", ").join(paramEntry.getValue());
                result.append(paramValues).append('|');
            }
            log.trace(result.toString());
        }
        SchedulingEnum periodicityEnum = getPeriodicity(request);
        switch (periodicityEnum) {
            case DAILY:
                log.trace("Daily periodicity requested");
                return PeriodFactory.daily();
            case DATE:
                final String date = request.getParameter(KEY_DATE);
                log.trace("Date periodicity requested: date={}", date);
                try {
                    return PeriodFactory.day(date);
                } catch (ParseException e) {
                    throw new InvalidUserInputException("Unable to parse date: " + date, e);
                }
            case DAY_IN_MONTH_VALUE:
                final String dayInMonthStr = request.getParameter(SchedulingEnum.DAY_IN_MONTH_VALUE.getCriteriaName());
                log.trace("Monthly periodicity requested: day in month={}", dayInMonthStr);
                try {
                    int dayInMonth = Integer.parseInt(dayInMonthStr);
                    return PeriodFactory.dayInMonth(dayInMonth);
                } catch (NumberFormatException e) {
                    throw new InvalidUserInputException("Unable to parse day of month number (int): " + dayInMonthStr, e);
                }
            case WEEKEND:
                final String weekend = request.getParameter(SchedulingEnum.WEEKEND.getCriteriaName());
                log.trace("Monthly periodicity requested: weekend={}", weekend);
                return PeriodFactory.weekend(weekend);
            case WEEKLY:
                final String weekdayNum = request.getParameter(SchedulingEnum.WEEKLY.getCriteriaName());
                log.trace("Weekly periodicity requested: weekDay={}", weekdayNum);
                try {
                    int dayInWeek = Integer.parseInt(weekdayNum);
                    return PeriodFactory.weekly(dayInWeek);
                } catch (NumberFormatException e) {
                    throw new InvalidUserInputException("Unable to parse day of week number (int): " + weekdayNum, e);
                }
            default:
                throw new InvalidUserInputException("Unable to understand periodicity value:" + request.getParameter(KEY_PERIOD));
        }
    }

    private SchedulingEnum getPeriodicity(HttpServletRequest request) {
        final String initialPeriodicity = request.getParameter(KEY_PERIOD);
        String periodicityValue = request.getParameter(initialPeriodicity);
        if (SchedulingEnum.DAY_IN_MONTH_VALUE.getCriteriaName().equals(periodicityValue)) {
            return SchedulingEnum.DAY_IN_MONTH_VALUE;
        } else {
            return SchedulingEnum.getEnum(initialPeriodicity);
        }
    }

    UserData identifyUser(final HttpServletRequest request) {
        // Obtain userName, and user email through authentication
        String userName = request.getRemoteUser();
        try {
            return ActiveDirectory.obtainUserData(userName);
        } catch (NoConnectionToActiveDirectoryException e) {
            log.warn("Could not connect to the AD to authenticate the user for scheduling, using GUEST", e);
        } catch (InvalidInputException e) {
            log.warn("AD Could not authenticate the user for scheduling, using GUEST", e);
        } catch (InvalidActiveDirectorySearchException e) {
            log.warn("Could not perform a search on the AD for scheduling, using GUEST", e);
        }
        // Guest user when authentication failed
        return ActiveDirectory.getGuestUser();
    }
}
