package com.capgemini.reports.database;

import com.capgemini.framework.common.DateHelper;
import com.capgemini.reports.ReportEnum;
import com.capgemini.reports.ReportLaunchTypeEnum;
import com.capgemini.reports.database.javabean.DbReport;
import com.capgemini.reports.database.javabean.DbSchedulingReport;
import com.capgemini.reports.database.javabean.DbUser;
import com.capgemini.reports.database.javabean.enumeration.ReportErrorTypeEnum;
import com.capgemini.reports.database.javabean.enumeration.ReportStateEnum;
import com.capgemini.reports.database.manager.AbstractHibernateManager;
import com.capgemini.reports.database.manager.MngReport;
import com.capgemini.reports.database.manager.MngSchedulingCriteriaVal;
import com.capgemini.reports.database.manager.MngSchedulingCriteriaVal.SchedulingCriteriaValue;
import com.capgemini.reports.database.manager.MngSchedulingReport;
import com.capgemini.reports.exceptions.InvalidUserInputException;
import com.capgemini.reports.exceptions.NotFoundException;
import com.capgemini.reports.execution.AbstractExecutionReport;
import com.capgemini.reports.periodicity.SchedulingPeriod;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import java.util.List;

/**
 * This class will allow to record every steps of the report's execution.
 */
@Slf4j
public final class DataBaseReportHelper {

  private DataBaseReportHelper() {
    // Nothing to do here...
  }

  /**
   * Create the database record for the launch of this immediate or delayed report.<br>
   * This method is called in the report's servlet and save an image of this report in database after submitting it via the IHM.
   *
   * @param aAbstractExecutionReport  will provide the report concerned
   * @param userWhoRequestedTheLaunch the user that requested the launch
   * @return the saved Report (image of the DB)
   */
  public static DbReport registerLaunch(final AbstractExecutionReport<?, ?> aAbstractExecutionReport, final DbUser userWhoRequestedTheLaunch) {
    DbReport theCreatedReport = null;
    MngReport theReportManager = new MngReport();
    String theCode = aAbstractExecutionReport.getReportIdentifier().getReportCode();
    String theUid = aAbstractExecutionReport.getReportTechnicalUid();
    ReportLaunchTypeEnum launchType = aAbstractExecutionReport.getLaunchType();

    // Specific status : must be WAIT or delayed reports or READY for immediate reports
    ReportStateEnum reportState = (ReportLaunchTypeEnum.DELAYED.equals(launchType)) ? ReportStateEnum.WAIT : ReportStateEnum.READY;

    // Insert data in database
    try {
      theCreatedReport = theReportManager.insertWithCriteriaAndOwner(theCode, theUid, reportState, aAbstractExecutionReport.getLaunchData(),
        userWhoRequestedTheLaunch);
      String launchTypeStr = null;
      switch (launchType) {
        case DELAYED:
          launchTypeStr = "Delayed report ";
          break;
        case IMMEDIATE:
          launchTypeStr = "Report ";
          break;
        case SCHEDULED:
          launchTypeStr = "Scheduled report ";
          break;
        case AUTOMATIC:
          launchTypeStr = "Automatic report "; // Shouldn't happen
          break;
      }
      log.info(launchTypeStr + theCode + " saved in database with UID: " + theUid);
    }
    catch (InvalidUserInputException e) {
      log.error("Unable to store the report in database", e);
    }

    return theCreatedReport;
  }

  /**
   * Update the database record of the delayed report.<br>
   * This method is called after waiting in Quartz queue.
   *
   * @param aReportTechnicalUid gives the report technical ID
   * @return the saved Report (image of the DB)
   * @throws NotFoundException if the report could not be found in the DB
   */
  public static DbReport updateDelayedLaunch(final String aReportTechnicalUid) throws NotFoundException {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      Transaction transaction = session.beginTransaction();
      MngReport theReportManager = new MngReport();
      // Find the report to update
      DbReport theExecutingReport = theReportManager.searchByUid(aReportTechnicalUid, session);
      theExecutingReport.setState(ReportStateEnum.READY.toString());
      theReportManager.update(session, theExecutingReport);
      transaction.commit();
      return theExecutingReport;
    }
    finally {
      session.close();
    }
  }

  /**
   * Create the database record for the launch of this scheduled report.<br>
   * This method is called in the scheduled servlet of the report and save an image of the scheduling template of report in database after submitting it via the IHM.
   *
   * @param aAbstractExecutionReport  will provide the report concerned
   * @param userWhoRequestedTheLaunch the user that requested the launch
   * @param schedulingCriteriaValues  List<{@link SchedulingCriteriaValue}> - all the criteria associated to the report
   * @param schedulingName            String - the unique scheduling name
   * @return the saved Report (image of the DB)
   */
  public static DbSchedulingReport registerScheduledTemplate(final AbstractExecutionReport<?, ?> aAbstractExecutionReport,
                                                             final DbUser userWhoRequestedTheLaunch, final List<SchedulingCriteriaValue> schedulingCriteriaValues, final String schedulingName) {
    MngSchedulingReport theReportManager = new MngSchedulingReport();
    ReportEnum theCode = aAbstractExecutionReport.getReportIdentifier();
    String theUid = aAbstractExecutionReport.getReportTechnicalUid();

    // Insert data in database
    DbSchedulingReport theCreatedReport = theReportManager.insertSchedulingReportWithCriteriaAndOwner(schedulingName, schedulingCriteriaValues,
      aAbstractExecutionReport.getLaunchData(), userWhoRequestedTheLaunch, theCode);

    log.info("Scheduled report {} saved in database with UID: {}", theCode, theUid);
    return theCreatedReport;
  }

  /**
   * Create the database record for the launch of this immediate or delayed report.<br>
   * This method is called in the report's servlet and save an image of this report in database after submitting it via the IHM.
   *
   * @param aAbstractExecutionReport   will provide the report concerned
   * @param usersWhoRequestedTheLaunch all the users that requested the launch
   * @param schedulingReportId         Long - the id of the scheduling if it exists
   * @return the saved Report (image of the DB)
   */
  public static DbReport registerScheduledLaunch(final AbstractExecutionReport<?, ?> aAbstractExecutionReport,
                                                 final List<DbUser> usersWhoRequestedTheLaunch, final Long schedulingReportId) throws InvalidUserInputException {
    String theCode = aAbstractExecutionReport.getReportIdentifier().getReportCode();
    String theUid = aAbstractExecutionReport.getReportTechnicalUid();

    // Insert data in database
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      Transaction transaction = session.beginTransaction();
      DbReport theCreatedReport = new MngReport().insertWithCriteriaAndOwners(session, theCode, theUid, ReportStateEnum.READY, aAbstractExecutionReport.getLaunchData(),
        usersWhoRequestedTheLaunch, schedulingReportId);
      transaction.commit();
      log.info("Scheduled report saved in database with UID: " + theUid);
      return theCreatedReport;
    }
    finally {
      session.close();
    }
  }

  /**
   * Create the database record for the start of the execution of this report.
   *
   * @param theReportManager    the {@link MngReport} which will provide this report's UID in the DB, and is able to update its infos.
   * @param aReportTechnicalUid gives the report technical ID
   * @return the saved Report (image of the DB), never null
   * @throws NotFoundException if the report Entry could not be found in the DB
   */
  public static DbReport registerStart(final MngReport theReportManager, final String aReportTechnicalUid) throws NotFoundException {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      Transaction transaction = session.beginTransaction();
      DbReport theExecutingReport = theReportManager.searchByUid(aReportTechnicalUid, session);
      theExecutingReport.setStartGenerationDate(DateHelper.getCurrentSqlTimestamp());
      theExecutingReport.setState(ReportStateEnum.EXECUTING.toString());
      theReportManager.update(session, theExecutingReport);
      transaction.commit();
      return theExecutingReport;
    }
    finally {
      session.close();
    }
  }

  /**
   * Create the database record for the successful completion of the execution of this report.
   *
   * @param theExecutingReport    the dbReport entry
   * @param fileNameWithExtension the file name of the created report
   * @param theReportManager      the {@link MngReport} which is able to update the report's infos.
   */
  public static void registerCompletion(final DbReport theExecutingReport, final String fileNameWithExtension, final AbstractHibernateManager<DbReport> theReportManager) {
    if (theExecutingReport != null) {
      SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
      Session session = sessionFactory.openSession();
      try {
        Transaction transaction = session.beginTransaction();
        theExecutingReport.setEndGenerationDate(DateHelper.getCurrentSqlTimestamp());
        theExecutingReport.setState(ReportStateEnum.FINISHED.toString());
        theExecutingReport.setFileName(fileNameWithExtension);
        theReportManager.update(session, theExecutingReport);
        transaction.commit();
      }
      finally {
        session.close();
      }
    }
  }

  /**
   * Save an error message for an executed report in the SB.
   *
   * @param anErrorMessage      Content of the error message for the current executed report
   * @param anErrorType         Type of error (FUNCTIONAL or TECHNICAL)
   * @param aReportTechnicalUid gives the report technical ID
   */
  public static void registerError(final String anErrorMessage, final ReportErrorTypeEnum anErrorType, final String aReportTechnicalUid) {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      Transaction transaction = session.beginTransaction();
      MngReport theReportManager = new MngReport();
      DbReport theExecutingReport = theReportManager.searchByUid(aReportTechnicalUid, session);
      theExecutingReport.setErrorMessage(anErrorMessage);
      theExecutingReport.setErrorType(anErrorType.toString());
      theExecutingReport.setState(ReportStateEnum.FAILED.toString());
      theExecutingReport.setEndGenerationDate(DateHelper.getCurrentSqlTimestamp());
      theReportManager.update(session, theExecutingReport);
      transaction.commit();
    }
    catch (NotFoundException e) {
      log.warn("Unable to find report " + aReportTechnicalUid + ", could not register this error: " +
        anErrorMessage, e);
    }
    finally {
      session.close();
    }
  }

  /**
   * Update the database record of a scheduled report.
   *
   * @param aSchedulingTemplateId the ID of the schedulingReport entry
   * @param report                the execution
   * @param schedulingName        the name of the scheduling (will overwrite the existing name
   * @param period                a {@link SchedulingPeriod} that will replace the existing periodicity
   * @param dbUser                the user who created r last updated the scheduling
   * @return the saved {@link DbSchedulingReport} (image of the DB)
   */
  public static DbSchedulingReport updateScheduling(final Long aSchedulingTemplateId, final AbstractExecutionReport<?, ?> report,
                                                    final String schedulingName, final SchedulingPeriod period, DbUser dbUser) {
    log.debug("Updating scheduling: {}({}) at {} for {}({})", aSchedulingTemplateId, schedulingName, period.toString(), dbUser.getId(),
      dbUser.getLogin());
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      Transaction transaction = session.beginTransaction();
      // Find the report to update with the scheduling id received from the IHM (selected by the user)
      MngSchedulingReport theReportManager = new MngSchedulingReport();
      DbSchedulingReport theScheduledReport = theReportManager.searchById(session, aSchedulingTemplateId);
      theScheduledReport.setLastUpdate(DateHelper.getCurrentSqlTimestamp()); // Update LastUpdate time
      theScheduledReport.setSchedulingName(schedulingName); // May have changed? Overwrite anyway
      theScheduledReport.setAdminResponsibleId(dbUser.getId()); // Update responsible user
      theReportManager.update(session, theScheduledReport);

      // Update all old criteria
      MngSchedulingCriteriaVal theSchedulingCriteriaValManager = new MngSchedulingCriteriaVal();
      theSchedulingCriteriaValManager.updatePeriod(session, aSchedulingTemplateId, period);
      theSchedulingCriteriaValManager.updateFunctionalParameters(session, aSchedulingTemplateId, report.getLaunchData());

      // Commit all changes in DB
      transaction.commit();
      return theScheduledReport;
    }
    finally {
      session.close();
    }
  }
}
