package com.capgemini.reports.formatting;

import com.capgemini.framework.common.SettingsHelper;
import com.capgemini.framework.common.activedirectory.ActiveDirectory;
import com.capgemini.reports.ReportEnum;
import com.capgemini.reports.common.scheduling.controller.SchedulingReaderServlet.SchedulingReportEntry;
import com.capgemini.reports.database.javabean.DbUser.UserData;
import com.capgemini.reports.database.manager.MngReportType;
import com.capgemini.reports.database.manager.MngSchedulingReport;
import com.capgemini.reports.exceptions.InvalidActiveDirectorySearchException;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.exceptions.NoConnectionToActiveDirectoryException;
import com.capgemini.reports.exceptions.NothingToBeShownException;
import com.google.common.base.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringEscapeUtils;
import org.hibernate.Session;

import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * This page displays all information on report schduling.
 * <p>
 * Top left is a filter on report codes<br>
 * Top right is a combobox that tells if the report selected in the filter will<br>
 * Below is a table listing all currently scheduled reports:
 * <ol>
 * <li>Report Code (R01 / R20...)</li>
 * <li>Scheduling name</li>
 * <li>Periodicity (weekly, monthly, etc.)</li>
 * <li>Last modification date</li>
 * <li>Owning administrator (final a User)</li>
 * <li>Next scheduled launch</li>
 * <li>A button opening a popup showing the report's Functional parameters</li>
 * <li>A button opening a page to edit the line's scheduling parameters</li>
 * <li>A button to delete the line's schedule altogether.</li>
 * </ol>
 */
@Slf4j
public class AllSchedulingReports extends AbstractTableFormatter {


  private static final boolean DEV_MODE = SettingsHelper.isDevMode();
  private static final int MAX_PAGE_SIZE = SettingsHelper.getJspPaging();

  private static final String QUOTE_WITH_END_COMMA = "' ; ";
  private static final String BUTTON_END = "</BUTTON>";

  private static final String TAG_SELECT_DATA = "SELECT_DATA";
  private static final String TAG_DISPLAY_BUTTONS = "DISPLAY_BUTTONS";
  private static final String TAG_ACTIF_REPORT_NAME = "ACTIF_REPORT_NAME";
  private static final String TAG_ACTIF_REPORT_VALUE = "ACTIF_REPORT_VALUE";
  private static final String TAG_ACTIF_BUTTON_VALIDATE = "ACTIF_BUTTON_VALIDATE";
  private static final String TAG_PAGING = "PAGING";
  /**
   * Date Format of the form 'dd-MMMM-yyyy' which gives '08-April-2014' in US timezone.
   */
  private static final String DD_MMMM_YYYY = "dd MMMM yyyy";
  private static final String SIMPLE_FORMATTER = DD_MMMM_YYYY;

  private final String theUserLogin;

  /**
   * The report filter. May be null far 'all'.
   */
  private final Optional<ReportEnum> reportCode;
  private final String thePageParamStr;
  private final Set<SchedulingReportEntry> entries;
  private String paging;

  /**
   * Constructs an instance of this page. Invoke {@link AllSchedulingReports#makeJsContent()} to generate it.<br>
   * This page displays all information on report scheduling.
   * <p>
   * Top left is a filter on report codes<br>
   * Top right is a combobox that tells if the report selected in the filter will<br>
   * Below is a table listing all currently scheduled reports:
   * <ol>
   * <li>Report Code (R01 / R20...)</li>
   * <li>Scheduling name</li>
   * <li>Periodicity (weekly, monthly, etc.)</li>
   * <li>Last modification date</li>
   * <li>Owning administrator (final a User)</li>
   * <li>Next scheduled launch</li>
   * <li>A button opening a popup showing the report's Functional parameters</li>
   * <li>A button opening a page to edit the line's scheduling parameters</li>
   * <li>A button to delete the line's schedule altogether.</li>
   * </ol>
   *
   * @param reportCode      the {@link ReportEnum} (maybe null for ALL)
   * @param thePageParamStr (optional) the page number.<br>
   *                        possible values: 1, 2, 3...
   * @param theUserLogin    the login of the user who requested the page
   * @param entries         a {@link Set} of {@link SchedulingReportEntry} obtained from the DB, that belong to the user
   */
  public AllSchedulingReports(final ReportEnum reportCode, final String thePageParamStr, final String theUserLogin,
                              final Set<SchedulingReportEntry> entries) {
    super();
    this.reportCode = Optional.fromNullable(reportCode);
    this.thePageParamStr = thePageParamStr;
    this.theUserLogin = theUserLogin;
    this.entries = entries;
  }

  private static int obtainIntegerPageParam(final String thePageParamStr) throws InvalidInputException {
    if (thePageParamStr != null) {
      try {
        return Integer.parseInt(thePageParamStr);
      }
      catch (NumberFormatException e) {
        throw new InvalidInputException("Num&eacute;ro de page incorrect", e);
      }
    }
    else {
      return 1;
    }
  }

  private static long pageCount(final long schedulingReportCount) {
    return (schedulingReportCount - 1) / MAX_PAGE_SIZE + 1; // Integer division
  }

  @Override
  protected String makeTable(Session session) throws InvalidInputException {
    // Reject request if not admin nor devmode
    if (!DEV_MODE) {
      boolean isAdmin = false; // by default
      try {
        UserData userData = ActiveDirectory.obtainUserData(theUserLogin);
        isAdmin = userData.isAdmin();
      }
      catch (InvalidInputException e) {
        log.warn("No login received, unable to authorize admin access", e);
      }
      catch (NoConnectionToActiveDirectoryException e) {
        log.warn("Could not connect to the AD to findAll users admin report ", e);
      }
      catch (InvalidActiveDirectorySearchException e) {
        log.warn("Could not perform a search on the AD, to findAll users admin report", e);
      }
      if (!isAdmin) {
        String aTechnicalErrorMessage = "User \"" + theUserLogin + "\" is not an administrator but requested the scheduling page";
        String userMessage =
          "L'utilisateur \"" + theUserLogin + "\" n'est pas un administrateur et ne peut accéder à la page de planification";
        throw new NothingToBeShownException(aTechnicalErrorMessage, userMessage);
      }
    }

    // Prepare data
    MngSchedulingReport mgnSchedulingReport = new MngSchedulingReport();
    final long schedulingReportCount;
    if (reportCode.isPresent()) {
      schedulingReportCount = mgnSchedulingReport.countSchedulingReportsByReportCode(session, reportCode.get());
    }
    else {
      schedulingReportCount = mgnSchedulingReport.countAllSchedulingReport(session);
    }
    int thePageParam = obtainIntegerPageParam(thePageParamStr);
    long pageCount = pageCount(schedulingReportCount);

    JsTableCreator reportArray = new JsTableCreator("reportArray");
    for (SchedulingReportEntry entry : entries) {
      String owner = entry.getUserResponsable();
      Long schedulingId = entry.getId();
      Optional<Date> nextFireTime = entry.getNextFireTime();
      SimpleDateFormat frenchDateFormat = new SimpleDateFormat(SIMPLE_FORMATTER, Locale.FRENCH);
      String nextFireTimeStr = nextFireTime.isPresent() ? frenchDateFormat.format(nextFireTime.get()) : "Jamais";
      String lastUpdate = frenchDateFormat.format(entry.getLastUpdate());

      // Make a line in the js table
      Map<String, String> schedulingReportsAsMap = new HashMap<>();
      schedulingReportsAsMap.put("report_code", StringEscapeUtils.escapeJavaScript(entry.getReportCode().getReportCode()));
      schedulingReportsAsMap.put("planif_name", StringEscapeUtils.escapeJavaScript(entry.getPlanificationName()));
      schedulingReportsAsMap.put("period", StringEscapeUtils.escapeJavaScript(entry.getPeriod()));
      schedulingReportsAsMap.put("date_maj", StringEscapeUtils.escapeJavaScript(lastUpdate));
      schedulingReportsAsMap.put("user_responsable", StringEscapeUtils.escapeJavaScript(owner));
      schedulingReportsAsMap.put("date_start", StringEscapeUtils.escapeJavaScript(nextFireTimeStr));
      schedulingReportsAsMap.put("id", StringEscapeUtils.escapeJavaScript(schedulingId.toString()));
      reportArray.addLine(schedulingReportsAsMap);
    }

    // Additional tags preparation
    paging = makePaging(thePageParam, pageCount);

    return reportArray.toString();
  }

  /* Paging */
  private String makePaging(final int thePageParam, final long theLastPage) {
    /* Manage page browsing */
    StringBuilder thePaging = new StringBuilder();
    for (int i = 1; (i <= theLastPage) && (theLastPage > 1); i++) {
      if (thePaging.length() > 0) {
        thePaging.append(" - ");
      }
      else {
        thePaging.append(
          "<div align=\"center\" width=\"90%\" style=\"border-bottom:3px double #EEEEFF;margin-bottom:3px;\"/>&nbsp;</div><b style=\"margin-left:500px;\" >Pages:</b> ");
      }

      if (i == thePageParam) {
        thePaging.append("<BUTTON class=\"formButton\" style=\"color:#22AA22;border:1px solid #338833;width:28px;padding:1px;\">").append(i)
          .append(BUTTON_END);
      }
      else {
        String sanitizedReportCode = reportCode.isPresent() ? reportCode.get().getReportCode() : "TOUS";
        thePaging
          .append("<BUTTON class=\"formButton\" style=\"color:#333355;border:1px solid #333355;width:28px;padding:1px;\" onclick=\"GoSchedulingPage(")
          .append(i).append(", '").append(sanitizedReportCode).append("') \">").append(i).append(BUTTON_END);
      }
    }
    return thePaging.toString();
  }

  @Override
  protected String successPageTemplateLocation() {
    return "/html/schedulingReports/schedulingReports_success.html";
  }

  @Override
  protected String failedPageTemplateLocation() {
    return "/html/schedulingReports/schedulingReports_error.html";
  }

  @Override
  protected void addOptionalTags(Session session) {
    this.setTagValue(TAG_SELECT_DATA, getDatasReportType(session));
    this.setTagValue(TAG_DISPLAY_BUTTONS, displayButton());
    this.setTagValue(TAG_ACTIF_BUTTON_VALIDATE, getStateButtonValider());
    this.setTagValue(TAG_PAGING, paging == null ? "" : paging);
    if (reportCode.isPresent()) {
      ReportEnum reportEnum = reportCode.get();
      this.setTagValue(TAG_ACTIF_REPORT_NAME, "var activeReportCode='" + reportEnum.getReportCode() + QUOTE_WITH_END_COMMA);
      String authorizedStr = "TRUE".equals(isSchedulingAuthorized(session, reportEnum)) ? "OUI" : "NON";
      this.setTagValue(TAG_ACTIF_REPORT_VALUE, "var isReportAuthorized='" + authorizedStr + QUOTE_WITH_END_COMMA);
    }
    else {
      this.setTagValue(TAG_ACTIF_REPORT_NAME, "var activeReportCode='TOUS" + QUOTE_WITH_END_COMMA);
      this.setTagValue(TAG_ACTIF_REPORT_VALUE, "var isReportAuthorized='NON" + QUOTE_WITH_END_COMMA);
    }
  }

  /* Display or not button Valider */
  private String getStateButtonValider() {
    return (reportCode.isPresent()) ? "" : "display: none;";
  }

  /* Show or hide button "Nouveau" if value != "TOUS" and show button "Annuler" */
  private String displayButton() {
    StringBuilder showButton = new StringBuilder();
    if (reportCode.isPresent()) {
      showButton.append(
        "<td><button id=\"buttonNewPlanification\" class=\"formButton\" style=\" float:right;\" onclick=\"newSchedulingReport()\">Nouveau</button></td>");
      showButton.append("<td><button class=\"formButton\" onclick=\"closeWindow()\">Annuler</button></td>");
    }
    else {
      showButton.append("<td align=\"center\"><button class=\"formButton\" onclick=\"closeWindow()\">Annuler</button></td>");
    }
    return showButton.toString();
  }

  /**
   * Return js code for an array containing 'TOUS' as well as all report types authorized for planification.
   */
  private String getDatasReportType(Session session) {
    MngReportType theMngReportType = new MngReportType();
    Collection<String> theReportType = theMngReportType.getAllSchedulableReportTypes(session);
    JsArrayCreator selectArray = new JsArrayCreator("selectArray");
    selectArray.addEntry("TOUS");
    selectArray.addAll(theReportType);
    return selectArray.toString();
  }

  private String isSchedulingAuthorized(Session session, final ReportEnum aReportCode) {
    return new MngReportType().search(session, aReportCode).getReportAuthorized();
  }

  @Override
  protected void addOptionalErrorTags() {
    // Nothing to do for Running Reports
  }

}
