package com.capgemini.reports.common;

import com.capgemini.framework.common.StringHelper;
import com.capgemini.framework.common.TeamcenterDateFormatter;
import com.capgemini.framework.common.activedirectory.ActiveDirectory;
import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.reports.checkdata.CheckingDataHelper;
import com.capgemini.reports.database.DataBaseReportHelper;
import com.capgemini.reports.database.HibernateUtil;
import com.capgemini.reports.database.javabean.DbReport;
import com.capgemini.reports.database.javabean.DbUser;
import com.capgemini.reports.database.javabean.DbUser.UserData;
import com.capgemini.reports.database.manager.MngCriteriaVal.CriteriaValue;
import com.capgemini.reports.database.manager.MngReport;
import com.capgemini.reports.database.manager.MngReport.RunningReportEntry;
import com.capgemini.reports.database.manager.MngReportOwner;
import com.capgemini.reports.database.manager.MngUser;
import com.capgemini.reports.exceptions.*;
import com.capgemini.reports.execution.AbstractExecutionReport;
import com.capgemini.reports.execution.posting.DelayedPostingStategy;
import com.capgemini.reports.execution.posting.ImmediatePostingStrategy;
import com.capgemini.reports.logging.ReportLoggerHelper;
import com.capgemini.reports.quartz.SchedulerServer;
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.jms.JMSException;
import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * Abstract class providing the framework for submitting and checking the necessary parameters to produce a report.
 *
 * @param <T> the sub-class of AbstractExecutionReport that this servlet will generate
 */
@Slf4j
public abstract class AbstractSubmitServlet<T extends AbstractExecutionReport<T, ?>> extends AbstractKurtServlet {
  private static final long serialVersionUID = -9065306729627250134L;

  @Getter
  @EJB
  protected SchedulerServer schedulingServer;

  @Override
  protected JSONObject perform(final HttpServletRequest request, final TcSession tcSession) throws JMSException,
    InvalidUserInputException, NamingException, SchedulerException {
    // Identify the user using the LDAP
    UserData adUser = identifyUser(request);

    // Prepare the report execution parameters
    AbstractExecutionReport<?, ?> report = prepareReport(request);
    List<CriteriaValue> launchData = report.getLaunchData();
    String reportCode = report.getReportIdentifier().getReportCode();

    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      // Find / update / create corresponding user in DB
      DbUser dbUser = new MngUser().fetchOrUpdateOrCreateUser(session, adUser);
      log.info("Server received a request to perform a {} report from user {} with parameters: {}", reportCode, dbUser.getLogin(), launchData);

      // Verify that the report has not been already requested not too long ago
      List<RunningReportEntry> duplicates = new MngReport().searchQueuedReportsWithCriteria(session, report.getReportIdentifier(), launchData);
      if (!duplicates.isEmpty()) {
        // Previous report execution detected: this report is a duplicate
        if (userOwnsOneReport(dbUser, duplicates)) {
          // Forbidding same user to submit report twice
          throw new DuplicateReportEntryFound();
        }
        RunningReportEntry dupe = duplicates.get(0);
        // Subscribing user to existing report execution
        new MngReportOwner().subscribe(session, dbUser, dupe);
        if (dbUser.getEmail() == null || dbUser.getEmail().isEmpty()) {
          // This user does not have an email Address, send a warning email to the user
          log.warn("Cet utilisateur abonné au résultat de ce rapport n’a pas d’adresse mail : {} {}", report.getReportIdentifier().getReportName(), dbUser.getLogin());
        }
        throw new DuplicateReportEntryFound(dupe.getSafeName());
      }

      // Do a quick Check. If it fails, the exception thrown will carry a message to the user's webpage
      log.debug("Report {} is not a duplicate, Beginning quick checks...", reportCode);
      try {
        CheckingDataHelper.doQuickCheck(tcSession, report);
      }
      catch (FileLoadingException e) {
        log.error("Policy file could not be read", e);
        return ServletMessageHelper.errorMessage(e);
      }
      log.debug("All quick checks have passed for {}", reportCode);

      // Register current time as the posting time
      report.setPostingTime(TeamcenterDateFormatter.getCurrentTime());

      // Perform the requested strategy: post immediately, or wait some delay
      switch (report.getLaunchType()) {
        case DELAYED:
          new DelayedPostingStategy(schedulingServer).handleRequest(report);
          break;
        case IMMEDIATE:
          new ImmediatePostingStrategy().handleRequest(report);
          break;
        default:
          final String msg =
            StringHelper.concatToString("Unable to perform the SUBMIT request, the execution strategy ", report.getLaunchType()
              .toString(), " is unknwon for report ", report.getReportTechnicalUid());
          throw new UnsupportedOperationException(msg);
      }

      // Record the report launch in the database, and log this
      DbReport theReportEntry = DataBaseReportHelper.registerLaunch(report, dbUser);

      // Log the report request (AFTER the email is sent, so that the report knows its startTime)
      ReportLoggerHelper.logRequest(dbUser, theReportEntry, report);

      // Return a success message
      return sendSuccessMsg(report);
    }
    finally {
      session.close();
    }
  }

  private JSONObject sendSuccessMsg(final AbstractExecutionReport<?, ?> report) {
    switch (report.getLaunchType()) {
      case DELAYED:
        // Return a nice success message to the user
        return ServletMessageHelper.successMessage("Le rapport \"" + report.getReportIdentifier().getReportTitle() +
          "\" est différé");
      default:
        // This case is for IMMEDIATE type
        return ServletMessageHelper.successMessage("Le rapport \"" + report.getReportIdentifier().getReportTitle() +
          "\" a bien été soumis");
    }
  }

  private boolean userOwnsOneReport(final DbUser dbUser, final List<RunningReportEntry> duplicates) {
    for (RunningReportEntry report : duplicates) {
      if (dbUser.getLogin() != null && dbUser.getLogin().equals(report.getLogin())) {
        return true;
      }
    }
    return false;
  }

  private 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, using GUEST", e);
    }
    catch (InvalidInputException e) {
      log.warn("AD Could not authenticate the user, using GUEST", e);
    }
    catch (InvalidActiveDirectorySearchException e) {
      log.warn("Could not perform a search on the AD, using GUEST", e);
    }
    // Guest user when authentication failed
    return ActiveDirectory.getGuestUser();
  }

  /**
   * Provide the {@link AbstractExecutionReport} with the parameters.
   *
   * @param request the client request.
   * @return the report execution, with its parameters checked, to be posted in the queues.
   */
  public abstract T prepareReport(final HttpServletRequest request);
}
