package com.capgemini.reports.execution.strategies;

import com.capgemini.framework.teamcenter.authent.TcSession;
import com.capgemini.framework.teamcenter.exceptions.KurtException;
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.manager.MngReport;
import com.capgemini.reports.database.manager.MngUser;
import com.capgemini.reports.exceptions.InvalidUserInputException;
import com.capgemini.reports.exceptions.NoConfChangesException;
import com.capgemini.reports.exceptions.NotFoundException;
import com.capgemini.reports.exceptions.ReportPrintException;
import com.capgemini.reports.execution.AbstractExecutionReport;
import com.capgemini.reports.execution.actions.*;
import com.capgemini.reports.printing.PrintParameters;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * This class is an abstract class which is used to manage execution behaviors to reports.<br>
 * There are two different behaviors for now :<br>
 * <ul>
 * <li>The immediate or delayed strategy.
 * <li>The scheduled strategy.
 * </ul>
 */
@AllArgsConstructor
@EqualsAndHashCode
public abstract class AbstractExecutionStrategy implements IExecutionStrategy, Serializable {
  private static final long serialVersionUID = -4156807152530226260L;
  private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExecutionStrategy.class);

  private final IPreExecutionStep preAction;
  private final IPostExecutionStep postAction;
  private final IErrorExecutionStep errorAction;

  /**
   * Constructs a default strategy, with default pre/post/error actions.
   */
  AbstractExecutionStrategy() {
    this.preAction = defaultPreAction();
    this.postAction = defaultPostAction();
    this.errorAction = defaultErrorAction();
  }

  /**
   * Default pre-execution action.
   * <ol>
   * <li>Update AbstractExecutionReport state (start generation date).</li>
   * <li>Register report execution start in the DB.</li>
   * <li>Send start email to report owners</li>
   * <li>Log report Start event in special log file</li>
   * <li>log effective start in technical log</li>
   * </ol>
   */
  private static CompositePreAction defaultPreAction() {
    return new CompositePreAction(new UpdateReportAction(), new UpdateReportInDbAction(), new LoggingAction());
  }

  /**
   * Default post-execution action.
   * <ol>
   * <li>Update AbstractExecutionReport state (end generation date).</li>
   * <li>Register report execution end in the DB.</li>
   * <li>Send end email to report owners (include the location where the report can be retrieved as a download link)</li>
   * <li>Log report End event in special log file</li>
   * <li>log effective end in technical log</li>
   * </ol>
   */
  private static CompositePostAction defaultPostAction() {
    return new CompositePostAction(new UpdateReportAction(), new UpdateReportInDbAction(), new InteractiveEmailAction(), new LoggingAction());
  }

  /**
   * Default Error action.
   * <ol>
   * <li>Update AbstractExecutionReport state (error date).</li>
   * <li>Register report execution failure in the DB.</li>
   * <li>Send end email to report owners and administrators</li>
   * <li>Log report Failure event in special log file</li>
   * <li>log effective end in technical log</li>
   * </ol>
   */
  private static CompositeErrorAction defaultErrorAction() {
    return new CompositeErrorAction(new UpdateReportAction(), new UpdateReportInDbAction(), new InteractiveEmailAction(), new LoggingAction());
  }

  @Override
  public <T extends AbstractExecutionReport<T, ?>> void preExecutionStep(T report, TcSession tcSession) throws KurtException {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      MngReport mngReport = new MngReport();
      DbReport dbReport = mngReport.searchByUid(report.getReportTechnicalUid(), session);
      preAction.performPreAction(report, dbReport, tcSession);
    }
    finally {
      session.close();
    }
  }

  @Override
  public <T extends AbstractExecutionReport<T, ?>> void postExecutionStep(final T report, final String fileNameWithExtension,
                                                                          TcSession tcSession) throws KurtException {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      MngReport mngReport = new MngReport();
      DbReport dbReport = mngReport.searchByUid(report.getReportTechnicalUid(), session);
      postAction.performPostAction(tcSession, report, dbReport, fileNameWithExtension);
    }
    finally {
      session.close();
    }
  }

  @Override
  public File print(final PrintParameters printParameters, final AbstractExecutionReport<?, ?> report) throws KurtException {
    // Only print the report
    // No need to add specific behaviors contrary to ScheduledStrategy
    return printReport(printParameters, report);
  }

  @Override
  public String getReportFileName(final AbstractExecutionReport<?, ?> report) {
    return report.getSpecifiedFileName();
  }

  File printReport(final PrintParameters printParameters, final AbstractExecutionReport<?, ?> report) throws KurtException {
    try {
      return report.getPrinter().print(printParameters);
    }
    catch (ReportPrintException e) {
      throw new KurtException("JASPER print failed ", e);
    }
  }

  /**
   * Takes care of any <b>technical</b> (unexpected) error.<br>
   * Searches the DB for report owners, then lets the on-board {@link IErrorExecutionStep} deal with it.
   *
   * @param report       the report which just crashed
   * @param theThrowable the exception that caused all the turmoil
   */
  @Override
  public void handleTechnicalError(final AbstractExecutionReport<?, ?> report, final Throwable theThrowable) {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      DbReport dbReport = new MngReport().searchByUid(report.getReportTechnicalUid(), session);
      List<DbUser> owners = new MngUser().searchReportOwners(session, dbReport);
      errorAction.performTechnicalErrorAction(report, owners, theThrowable);
    }
    catch (NotFoundException e) {
      LOGGER.warn("Handling a prior technical error, could not find the original owners UID=" + report.getReportTechnicalUid(), e);
    }
    finally {
      session.close();
    }
  }

  /**
   * Takes care of any <b>functional</b> (unexpected) error.<br>
   * Searches the DB for report owners, then lets the on-board {@link IErrorExecutionStep} deal with it.
   *
   * @param report the report which just crashed
   * @param error  the exception that caused all the turmoil
   */
  @Override
  public void handleFunctionalError(final AbstractExecutionReport<?, ?> report, final NoConfChangesException error) {
    LOGGER.info(error.getMessage());
    errorAction.performFunctionalErrorAction(report, owners(report), error);
  }

  /**
   * Takes care of any <b>functional</b> (unexpected) error.<br>
   * Searches the DB for report owners, then lets the on-board {@link IErrorExecutionStep} deal with it.
   *
   * @param report the report which just crashed
   * @param error  the exception that caused all the turmoil
   */
  @Override
  public void handleFunctionalError(final AbstractExecutionReport<?, ?> report, final InvalidUserInputException error) {
    errorAction.performFunctionalErrorAction(report, owners(report), error);
  }

  protected List<DbUser> owners(AbstractExecutionReport<?, ?> report) {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      DbReport dbReport = new MngReport().searchByUid(report.getReportTechnicalUid(), session);
      return new MngUser().searchReportOwners(session, dbReport);
    }
    catch (NotFoundException e) {
      LOGGER.warn("Handling a prior technical error, could not find the original owners of report with UID=" + report.getReportTechnicalUid(), e);
      return new ArrayList<>();
    }
    finally {
      session.close();
    }
  }
}
