package com.capgemini.reports.common.scheduling.controller;

import com.capgemini.reports.ReportEnum;
import com.capgemini.reports.common.ServletMessageHelper;
import com.capgemini.reports.database.HibernateUtil;
import com.capgemini.reports.database.javabean.DbReportType;
import com.capgemini.reports.database.javabean.DbSchedulingReport;
import com.capgemini.reports.database.manager.MngReportType;
import com.capgemini.reports.database.manager.MngSchedulingCriteriaVal;
import com.capgemini.reports.database.manager.MngSchedulingReport;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.periodicity.SchedulingPeriod;
import com.capgemini.reports.quartz.SchedulerServer;
import com.google.common.base.Optional;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.json.JSONObject;
import org.quartz.SchedulerException;

import javax.ejb.EJB;
import javax.servlet.RequestDispatcher;
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 java.io.IOException;
import java.sql.Timestamp;
import java.util.*;
import java.util.Map.Entry;

/**
 * Controller servlet used during all transactions made to show the schedulings summary page.<br>
 * Entry parameters:
 * <ul>
 * <li><b>reportCode</b> (MANDATORY) contains a usueal report code ('R01', 'R19') or 'TOUS' to show all report schedulings.</li>
 * <li><b>page</b> (OPTIONAL) may contain an integer for the result page number [1..n]</li>
 * </ul>
 * This servlet correlates the DB entries and the quartz entries to produce a {@link Set} of {@link SchedulingReportEntry}.<br>
 * The request is enriched with this Set, and forwarded to a JSP for printing.
 * <p>
 * Output attributes:
 * <ul>
 * <li><b>schedulings</b> : contains a {@link List} of {@link SchedulingReportEntry}</li>
 * </ul>
 */
@Slf4j
@WebServlet("/schedulingReader")
public class SchedulingReaderServlet extends HttpServlet {
  private static final long serialVersionUID = -8848572431950894775L;

  private static final String FR_HUMAN_DATE = "Par date";
  private static final String FR_HUMAN_MONTHLY = "Tous les mois";
  private static final String FR_HUMAN_WEEKLY = "Toutes les semaines";
  private static final String FR_HUMAN_DAILY = "Tous les jours";

  // Key to output attributes
  private static final String KEY_SCHEDULINGS = "schedulings";

  private static final String CONTENT_TYPE_JSON = "application/json";
  private static final String FORMATTING_JSP_URL = "/frames/all_scheduling_reports.jsp";

  private static final String PARAM_REPORT_CODE = "reportCode";
  private static final String PARAM_PAGE = "page";

  @EJB
  SchedulerServer quartz;

  @Override
  protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
    // Parse server request parameters
    try {
      perform(request, response);
      JSONObject successMessage = ServletMessageHelper.successMessage("success");
      response.setContentType(CONTENT_TYPE_JSON);
      response.getWriter().write(successMessage.toString());
    }
    catch (InvalidInputException 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 (SchedulerException e) {
      // An error message will be sent to the user to tell him an error occurred
      log.error(e.getMessage(), e);
      response.setContentType(CONTENT_TYPE_JSON);
      response.getWriter().write(ServletMessageHelper.errorMessage(e.getMessage()).toString());
    }
  }

  @Override
  protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
    doPost(request, response);
  }

  /**
   * Performs the request: obtains data then forwards to a JSP for printing.
   *
   * @param request  the http request
   * @param response the http response
   * @throws ServletException      if the forward to JSP action fails
   * @throws IOException           if the forward to JSP action fails
   * @throws SchedulerException    if unable to schedule the report
   * @throws InvalidInputException if something went wrong while parsing TEMPORAL parameters (technical error)
   */
  private void perform(final HttpServletRequest request, final HttpServletResponse response)
    throws ServletException, IOException, SchedulerException, InvalidInputException {
    String reportCodeParamStr = request.getParameter(PARAM_REPORT_CODE);
    String pageStr = request.getParameter(PARAM_PAGE);

    // Parse page num
    int page;
    if (pageStr != null) {
      page = Integer.parseInt(pageStr);
    }
    else {
      page = 1;
    }

    // Obtain the db data
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      Map<DbSchedulingReport, String> dbData;
      MngSchedulingReport theMngSchedRep = new MngSchedulingReport();
      if (reportCodeParamStr != null && !"TOUS".equals(reportCodeParamStr)) {
        ReportEnum reportCodeParam = ReportEnum.fromCode(reportCodeParamStr);
        dbData = theMngSchedRep.searchSchedulingReportByReportCode(session, reportCodeParam, page);
      }
      else {
        dbData = theMngSchedRep.searchAllSchedulingReports(session, page);
      }

      // Obtain all reports currently queueing
      MngSchedulingCriteriaVal mngSchedulingCriteriaValues = new MngSchedulingCriteriaVal();
      Set<SchedulingReportEntry> schedulings = new TreeSet<>();
      for (Entry<DbSchedulingReport, String> entry : dbData.entrySet()) {
        DbSchedulingReport dbSchedulingReport = entry.getKey();
        String owner = entry.getValue();
        Long schedulingId = dbSchedulingReport.getId();
        // Find additional scheduling data
        Optional<Date> nextFireTime = quartz.nextExecutionOfJob(schedulingId);
        SchedulingPeriod period = mngSchedulingCriteriaValues.getPeriod(session, schedulingId);
        String periodStr = parseToFrench(period);

        // Find authorization status
        String reportEntryCode = dbSchedulingReport.getReportCode();
        ReportEnum reportEntryEnum = ReportEnum.fromCode(reportEntryCode);
        DbReportType dbReporttype = new MngReportType().search(session, reportEntryEnum);
        String authorized = dbReporttype.getReportAuthorized();
        boolean isReportAuthorized = "true".equalsIgnoreCase(authorized);

        // Make a line with all this data
        schedulings.add(new SchedulingReportEntry(schedulingId, dbSchedulingReport.getSchedulingName(), reportEntryEnum,
          dbSchedulingReport.getLastUpdate(), owner, periodStr, nextFireTime, isReportAuthorized));
      }

      // Add data to the request
      request.setAttribute(KEY_SCHEDULINGS, schedulings);
      // Forward to the relevant JSP
      RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(FORMATTING_JSP_URL);
      dispatcher.forward(request, response);
    }
    finally {
      session.close();
    }
  }

  private static String parseToFrench(final SchedulingPeriod period) {
    String periodStr;
    switch (period.getPeriodicity()) {
      case DAILY:
        periodStr = FR_HUMAN_DAILY;
        break;
      case DATE:
        periodStr = FR_HUMAN_DATE;
        break;
      case DAY_IN_MONTH_VALUE:
        periodStr = FR_HUMAN_MONTHLY;
        break;
      case WEEKEND:
        periodStr = FR_HUMAN_MONTHLY;
        break;
      case WEEKLY:
        periodStr = FR_HUMAN_WEEKLY;
        break;
      default:
        periodStr = "Unknown periodicity";
        break;
    }
    return periodStr;
  }

  /**
   * Represents a scheduling report entry.
   */
  @Getter
  @AllArgsConstructor
  public static class SchedulingReportEntry implements Comparable<SchedulingReportEntry> {

    // Data
    private final Long id;
    private final String planificationName;
    private final ReportEnum reportCode;
    private final Timestamp lastUpdate;
    private final String userResponsable;
    private final String period;
    private final Optional<Date> nextFireTime;
    private final boolean isReportAuthorized;

    @Override
    public String toString() {
      return "SchedulingReportEntry [id=" + id + ", planificationName=" + planificationName + ", reportCode=" + reportCode + ", lastUpdate="
        + lastUpdate + ", userResponsable=" + userResponsable + ", period=" + period + ", nextStart=" + nextFireTime
        + ", isReportAuthorized=" + isReportAuthorized + "]";
    }

    @Override
    public int compareTo(final SchedulingReportEntry other) {
      return this.getPlanificationName().compareTo(other.getPlanificationName());
    }
  }
}
