package com.capgemini.reports.database.manager;

import com.capgemini.framework.common.DateHelper;
import com.capgemini.framework.common.SettingsHelper;
import com.capgemini.reports.ReportEnum;
import com.capgemini.reports.database.HibernateUtil;
import com.capgemini.reports.database.javabean.DbCriteriaVal;
import com.capgemini.reports.database.javabean.DbSchedulingCriteriaVal;
import com.capgemini.reports.database.javabean.DbSchedulingReport;
import com.capgemini.reports.database.javabean.DbUser;
import com.capgemini.reports.database.manager.MngCriteriaVal.CriteriaValue;
import com.capgemini.reports.database.manager.MngSchedulingCriteriaVal.SchedulingCriteriaValue;
import com.capgemini.reports.exceptions.NotFoundException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * Hibernate manager for {@link DbSchedulingReport} JAVA bean.
 **/
@Getter
@Slf4j
public class MngSchedulingReport extends AbstractHibernateManager<DbSchedulingReport> {

  private static final int REPORT_CODE_POS = 0;
  private static final int END_GENERATION_DATE_POS = 1;
  private static final int FILE_NAME_POS = 2;

  // Column name in Db
  private static final String COLUMN_SCHEDULING_NAME = "schedulingName";

  // Key values
  private static final String KEY_REPORT_CODE = "reportCode";
  private static final String KEY_UPPER_DATE = "upperDate";

  private static final int MAX_JSP_PAGE_SIZE = SettingsHelper.getJspPaging();

  // Reports and their owners
  private static final String QUERY_ALL_SCHEDULING_REPORTS_AND_OWNERS;
  private static final String QUERY_SCHEDULING_REPORTS_BY_TYPE;

  // Reports by their periodicity
  private static final String QUERY_SCHEDULING_REPORTS_COUNT;
  private static final String QUERY_SCHEDULING_REPORTS_COUNT_BY_REPORT_CODE;
  private static final String QUERY_DELETE_SCHEDULING_REPORT_AND_CRITERIA_VAL;
  private static final String QUERY_SELECT_SCHEDULING_REPORT_AND_CRITERIA_VAL;

  static {
    String select = "SELECT count(*) ";
    String from = "FROM DbSchedulingReport T0 ";
    String whereReportCode = "WHERE T0.reportCode = :" + KEY_REPORT_CODE;
    QUERY_SCHEDULING_REPORTS_COUNT = select + from;
    QUERY_SCHEDULING_REPORTS_COUNT_BY_REPORT_CODE = QUERY_SCHEDULING_REPORTS_COUNT + whereReportCode;
  }

  static {
    String delete = "DELETE T0, T3";
    String select = "SELECT DISTINCT T0.reportCode ,T0.endGenerationDate, T0.fileName ";
    String from = "FROM DbReport T0, DbReportOwner T1,DbCriteriaVal T3 ";
    String whereCoherence = "WHERE T0.id = T1.reportId AND T0.id = T3.reportId ";
    String andBefore = "AND T0.endGenerationDate < :" + KEY_UPPER_DATE;
    String andIsaReportScheduling = "AND T0.schedulingReportId IS NOT NULL ";

    final String allSchedulingReports = from + whereCoherence + andBefore + andIsaReportScheduling;
    QUERY_DELETE_SCHEDULING_REPORT_AND_CRITERIA_VAL = delete + allSchedulingReports;
    QUERY_SELECT_SCHEDULING_REPORT_AND_CRITERIA_VAL = select + allSchedulingReports;
  }

  static {
    String select = "SELECT DISTINCT T0, concat(T1.firstname, ', ', T1.surname) ";
    String from = "FROM DbSchedulingReport T0, DbUser T1 ";
    String whereCoherence = "WHERE T0.adminResponsibleId = T1.id ";
    String andReportCode = "AND T0.reportCode = :reportCode ";
    String orderBy = "ORDER BY T0.lastUpdate DESC ";

    QUERY_SCHEDULING_REPORTS_BY_TYPE = select + from + whereCoherence + andReportCode + orderBy;
    QUERY_ALL_SCHEDULING_REPORTS_AND_OWNERS = select + from + whereCoherence + orderBy;
  }

  /**
   * Count in database the scheduling report list by report code .
   */
  public Long countSchedulingReportsByReportCode(Session session, final ReportEnum report) {
    Query theQuery = session.createQuery(QUERY_SCHEDULING_REPORTS_COUNT_BY_REPORT_CODE);
    theQuery.setString(KEY_REPORT_CODE, report.getReportCode());
    return (Long) theQuery.uniqueResult();
  }

  /**
   * Count in database all scheduling report list .
   */
  public Long countAllSchedulingReport(Session session) {
    Query theQuery = session.createQuery(QUERY_SCHEDULING_REPORTS_COUNT);
    return (Long) theQuery.uniqueResult();
  }

  /* Create a scheduling report */
  private DbSchedulingReport insert(Session session, final String schedulingName, final DbUser theAdminResponsible, final ReportEnum reportCode) {
    DbSchedulingReport schedulingReport = new DbSchedulingReport();

    schedulingReport.setSchedulingName(schedulingName);
    schedulingReport.setLastUpdate(DateHelper.getCurrentSqlTimestamp());
    schedulingReport.setAdminResponsibleId(theAdminResponsible.getId());
    schedulingReport.setReportCode(reportCode.getReportCode());
    // Persist object
    session.save(schedulingReport);
    return schedulingReport;
  }

  /**
   * Insert a SchedulingReport in the DB. <br>
   * The related {@link DbSchedulingReport}, {@link DbSchedulingCriteriaVal}s, {@link DbCriteriaVal}s will be created accordingly.
   *
   * @param schedulingName           the name of the scheduling, as asked by the user
   * @param schedulingCriteriaValues the temporal criterias of the scheduling
   * @param criteriaValues           the functional criteria of the report's execution
   * @param requestor                the user making the request
   * @param reportCode               the required report's type
   * @return the entry in the DB
   */
  public DbSchedulingReport insertSchedulingReportWithCriteriaAndOwner(final String schedulingName,
                                                                       final List<SchedulingCriteriaValue> schedulingCriteriaValues, final List<CriteriaValue> criteriaValues, final DbUser requestor,
                                                                       final ReportEnum reportCode) {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      Transaction transaction = session.beginTransaction();
      DbSchedulingReport dbSchedulingReport = this.insert(session, schedulingName, requestor, reportCode);
      Long schedulingReportId = dbSchedulingReport.getId();

      // save temporal criteria values
      MngSchedulingCriteriaVal theSchedulingCriteriaValManager = new MngSchedulingCriteriaVal();
      for (SchedulingCriteriaValue schedulingCriterion : schedulingCriteriaValues) {
        theSchedulingCriteriaValManager.insertByCriteriaName(session, schedulingCriterion.getName(), schedulingCriterion.getValue(), schedulingReportId);
      }

      // save functional criteria values
      MngSchedulingCriteriaVal theCriteriaValManager = new MngSchedulingCriteriaVal();
      for (CriteriaValue criterion : criteriaValues) {
        theCriteriaValManager.insertByCriteriaName(session, criterion.getName(), criterion.getValue(), schedulingReportId);
      }
      transaction.commit();
      return dbSchedulingReport;
    }
    finally {
      session.close();
    }
  }

  /**
   * Returns all data on all the scheduling report entries..<br>
   * Returns a {@link Map} whose keys are all the {@link DbSchedulingReport} of the DB, and each value is the associated owner's full name.
   */
  public Map<DbSchedulingReport, String> searchAllSchedulingReports(Session session, final int aPageNumber) {

    Query theQuery = session.createQuery(QUERY_ALL_SCHEDULING_REPORTS_AND_OWNERS);
    theQuery.setMaxResults(MAX_JSP_PAGE_SIZE);
    theQuery.setFirstResult(MAX_JSP_PAGE_SIZE * (aPageNumber - 1));

    Map<DbSchedulingReport, String> results = new HashMap<>();
    for (Object[] reportData : (List<Object[]>) theQuery.list()) {
      DbSchedulingReport dbReport = (DbSchedulingReport) reportData[0];
      String owner = (String) reportData[1];
      results.put(dbReport, owner);
    }
    return results;
  }

  /**
   * Returns all data on the scheduling report entries refering to the provided {@link ReportEnum} code.<br>
   * Returns a {@link Map} whose keys are all the {@link DbSchedulingReport} of the DB, and each value is the associated owner's full name.
   */
  public Map<DbSchedulingReport, String> searchSchedulingReportByReportCode(Session session, final ReportEnum reportCode, final int aPageNumber) {
    Query theQuery = session.createQuery(QUERY_SCHEDULING_REPORTS_BY_TYPE);
    theQuery.setMaxResults(MAX_JSP_PAGE_SIZE);
    theQuery.setFirstResult(MAX_JSP_PAGE_SIZE * (aPageNumber - 1));
    theQuery.setString(KEY_REPORT_CODE, reportCode.getReportCode());

    Map<DbSchedulingReport, String> results = new HashMap<>();
    for (Object[] reportData : (List<Object[]>) theQuery.list()) {
      DbSchedulingReport dbReport = (DbSchedulingReport) reportData[0];
      String owner = (String) reportData[1];
      results.put(dbReport, owner);
    }
    return results;
  }

  /**
   * Delete a scheduling report, and all its scheduling values (functional and temporal).
   *
   * @param schedulingReportId the ID f the scheduled report
   * @return the number of lines affected (all criteria values, plus the scheduling itself)
   * @throws NotFoundException if Hibernate is unable to perform the operation
   */
  public int deleteSchedulingReport(final Long schedulingReportId) throws NotFoundException {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      Transaction transaction = session.beginTransaction();
      DbSchedulingReport persistedObj = searchById(session, schedulingReportId);

      log.debug("Find DbSchedulingReport by id " + schedulingReportId);
      MngSchedulingCriteriaVal mngSchedulingCriteriaVal = new MngSchedulingCriteriaVal();
      int valuesDeleted = mngSchedulingCriteriaVal.deleteAllSchedulingCriteriaValueBySchedulingReportId(session, schedulingReportId);
      delete(session, persistedObj);
      transaction.commit();
      return valuesDeleted + 1;
    }
    catch (HibernateException e) {
      throw new NotFoundException("SchedulingReport with id " + schedulingReportId + " not found in DB.", e);
    }
    finally {
      session.close();
    }
  }

  @Override
  public Class<DbSchedulingReport> getManagedClass() {
    return DbSchedulingReport.class;
  }

  /**
   * Returns the entry with the provided scheduling_name (if any).<br>
   * If non exist, returns <code>null</code>
   */
  public DbSchedulingReport searchByName(Session session, String planifName) {
    return searchBy(session, COLUMN_SCHEDULING_NAME, planifName);
  }

}
