package com.capgemini.reports.database.manager;

import com.capgemini.reports.database.HibernateUtil;
import com.capgemini.reports.database.javabean.DbCriteria;
import com.capgemini.reports.database.javabean.DbSchedulingCriteriaVal;
import com.capgemini.reports.database.javabean.enumeration.ReportCriteriaEnum;
import com.capgemini.reports.database.javabean.enumeration.SchedulingEnum;
import com.capgemini.reports.database.manager.MngCriteriaVal.CriteriaValue;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.periodicity.PeriodFactory;
import com.capgemini.reports.periodicity.SchedulingPeriod;
import com.google.common.base.Joiner;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

/**
 * Hibernate manager for {@link DbSchedulingCriteriaVal} JAVA bean.
 **/
@Slf4j
public class MngSchedulingCriteriaVal extends AbstractHibernateManager<DbSchedulingCriteriaVal> {

  private static final String COLUMN_SCHEDULING_REPORT_ID = "schedulingReportId";
  private static final String KEY_REPORT_ID = "schedulingReportId";

  private static final String QUERY_SELECT_CRITERIAS_VALUES;
  private static final String QUERY_SELECT_FUNCTIONAL_CRITERIAS_VALUES;
  private static final String QUERY_SELECT_FUNCTIONAL_CRITERIAS_DATA;
  private static final String QUERY_SELECT_TEMPORAL_CRITERIAS_VALUES;
  private static final String QUERY_SELECT_TEMPORAL_CRITERIAS_DATA;
  private static final String QUERY_DELETE_CRITERIAS_VALUES_OF_SCHEDULING;
  private static final String QUERY_SELECT_PERIOD;
  private static final String QUERY_DELETE_TEMPORAL_CRITERIAS_VALUES_OF_SCHEDULING;
  private static final String QUERY_DELETE_FUNCTIONAL_CRITERIAS_VALUES_OF_SCHEDULING;

  static {
    String select = "SELECT DISTINCT T0.criteriaName, T1.criteriaValue ";
    String from = "FROM DbSchedulingCriteriaVal T1, DbCriteria T0 ";
    String whereCoherence = "WHERE T1.dbCriteria = T0.id ";
    String andReportId = "AND T1.schedulingReportId = :" + KEY_REPORT_ID + " ";
    String andIsFunctional = "AND T0.isTemporel = 'FALSE' ";
    String andIsTemporal = "AND T0.isTemporel = 'TRUE' ";
    String orderBy = "ORDER BY T0.criteriaName ASC";
    QUERY_SELECT_FUNCTIONAL_CRITERIAS_DATA = select + from + whereCoherence + andReportId + andIsFunctional + orderBy;
    QUERY_SELECT_TEMPORAL_CRITERIAS_DATA = select + from + whereCoherence + andReportId + andIsTemporal + orderBy;
  }

  static {
    String select = "SELECT T0 ";
    String selectID = "SELECT T0.id ";
    String from = "FROM DbSchedulingCriteriaVal T0, DbCriteria T1 ";
    String whereCoherence = "WHERE T0.dbCriteria = T1.id  ";
    String andReportId = "AND T0.schedulingReportId = :" + KEY_REPORT_ID + " ";
    String andIsPeriodicity = "AND T1.criteriaName ='period' ";
    String andIsFunctional = "AND T1.isTemporel = 'FALSE' ";
    String andIsTemporal = "AND T1.isTemporel = 'TRUE' ";
    QUERY_SELECT_CRITERIAS_VALUES = select + from + whereCoherence + andReportId;
    QUERY_SELECT_PERIOD = select + from + whereCoherence + andReportId + andIsPeriodicity;
    QUERY_SELECT_FUNCTIONAL_CRITERIAS_VALUES = select + from + whereCoherence + andReportId + andIsFunctional;
    QUERY_SELECT_TEMPORAL_CRITERIAS_VALUES = select + from + whereCoherence + andReportId + andIsTemporal;

    String delete = "DELETE DbSchedulingCriteriaVal ";
    String whereIn = "WHERE id in (";
    String endWhere = ")";
    final String selectValueID = selectID + from + whereCoherence + andReportId;
    QUERY_DELETE_CRITERIAS_VALUES_OF_SCHEDULING = delete + whereIn + selectValueID + endWhere;
    QUERY_DELETE_FUNCTIONAL_CRITERIAS_VALUES_OF_SCHEDULING = delete + whereIn + selectValueID + andIsFunctional + endWhere;
    QUERY_DELETE_TEMPORAL_CRITERIAS_VALUES_OF_SCHEDULING = delete + whereIn + selectValueID + andIsTemporal + endWhere;
  }

  @Override
  protected Class<DbSchedulingCriteriaVal> getManagedClass() {
    return DbSchedulingCriteriaVal.class;
  }

  /**
   * Create a new scheduling criteria value.
   */
  private DbSchedulingCriteriaVal insert(Session session, final DbCriteria dbCriteria, final String criteriaValue, final Long schedulingReportId) {
    log.debug("Insert SchedulingCriteriaVal: {}({}) = {} for {}", dbCriteria.getId(), dbCriteria.getCriteriaName(), criteriaValue,
      schedulingReportId);

    DbSchedulingCriteriaVal theSchedulingCriteriaVal = new DbSchedulingCriteriaVal();

    theSchedulingCriteriaVal.setCriteriaValue(criteriaValue);
    theSchedulingCriteriaVal.setDbCriteria(dbCriteria);
    theSchedulingCriteriaVal.setSchedulingReportId(schedulingReportId);

    // Persist object
    session.save(theSchedulingCriteriaVal);

    return theSchedulingCriteriaVal;
  }

  /**
   * Create a new scheduling criteria value using the criteria name as key.
   */
  DbSchedulingCriteriaVal insertByCriteriaName(Session session, final String criteriaName, final String criteriaValue, final Long schedulingReportId) {
    // Search the corresponding criteria using its name
    DbCriteria theCriteria = new MngCriteria().searchByName(session, criteriaName);

    return insert(session, theCriteria, criteriaValue, schedulingReportId);
  }

  /**
   * Returns a {@link Map} of all {@link ReportCriteriaEnum} to their values, for a given report (whose ID is provided).
   */
  public Map<ReportCriteriaEnum, String> getFunctionalMap(final Long schedulingReportId) {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      List<DbSchedulingCriteriaVal> result = searchFunctionalCriteria(session, schedulingReportId);
      Map<ReportCriteriaEnum, String> theFunctionalValues = new EnumMap<>(ReportCriteriaEnum.class);
      for (DbSchedulingCriteriaVal criteria : result) {
        theFunctionalValues.put(ReportCriteriaEnum.valueOf(criteria), criteria.getCriteriaValue());
      }
      return theFunctionalValues;
    }
    finally {
      session.close();
    }
  }

  /**
   * Search functional criteria values or scheduling report.
   */
  public List<SchedulingCriteriaValue> searchFunctional(final Long schedulingReportId) {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      Query theQuery = session.createQuery(QUERY_SELECT_FUNCTIONAL_CRITERIAS_DATA);
      theQuery.setLong(KEY_REPORT_ID, schedulingReportId);

      List<SchedulingCriteriaValue> result = new ArrayList<>();
      for (Object[] reportData : (List<Object[]>) theQuery.list()) {
        result.add(new SchedulingCriteriaValue((String) reportData[0], (String) reportData[1]));
      }
      return result;
    }
    finally {
      session.close();
    }
  }

  /**
   * Search functional {@link DbSchedulingCriteriaVal} of scheduling report.
   */
  private List<DbSchedulingCriteriaVal> searchFunctionalCriteria(Session session, final Long schedulingReportId) {
    Query theQuery = session.createQuery(QUERY_SELECT_FUNCTIONAL_CRITERIAS_VALUES);
    theQuery.setLong(KEY_REPORT_ID, schedulingReportId);
    return castAll(theQuery.list());
  }

  /**
   * Search Temporal {@link DbSchedulingCriteriaVal} of scheduling report.
   */
  private List<DbSchedulingCriteriaVal> searchTemporalCriteria(Session session, final Long schedulingReportId) {
    Query theQuery = session.createQuery(QUERY_SELECT_TEMPORAL_CRITERIAS_VALUES);
    theQuery.setLong(KEY_REPORT_ID, schedulingReportId);
    return castAll(theQuery.list());
  }

  /**
   * Search All {@link DbSchedulingCriteriaVal} of scheduling report.
   */
  public List<DbSchedulingCriteriaVal> searchCriteria(Session session, final Long schedulingReportId) {
    Query theQuery = session.createQuery(QUERY_SELECT_CRITERIAS_VALUES);
    theQuery.setLong(KEY_REPORT_ID, schedulingReportId);
    return castAll(theQuery.list());
  }

  /**
   * Builds a SchedulingPeriod representing the scheduling parameters of the ReportSchedulig whose ID is passed as parameters.
   */
  public SchedulingPeriod getPeriod(Session session, final Long schedulingReportId) throws InvalidInputException {
    List<DbSchedulingCriteriaVal> result = searchTemporalCriteria(session, schedulingReportId);
    return PeriodFactory.fromDbSchedulingCriteria(result);
  }

  /**
   * Obtain the criteria which describe the period of a report.
   */
  public SchedulingCriteriaValue getPeriodOfSchedulingReportById(Session session, final Long schedulingReportId) {
    Query theQuery = session.createQuery(QUERY_SELECT_PERIOD);
    theQuery.setLong(KEY_REPORT_ID, schedulingReportId);

    // Construct query result
    List<SchedulingCriteriaValue> result = new ArrayList<>();
    for (DbSchedulingCriteriaVal reportData : (List<DbSchedulingCriteriaVal>) theQuery.list()) {
      String periodicityStr = reportData.getDbCriteria().getCriteriaName();
      SchedulingEnum periodicity = SchedulingEnum.getEnum(periodicityStr);
      String criteriaValue = reportData.getCriteriaValue();
      result.add(new SchedulingCriteriaValue(periodicity, criteriaValue));
    }

    if (!result.isEmpty()) {
      return result.get(0);
    }
    else {
      return new SchedulingCriteriaValue("", "");
    }
  }

  /**
   * Updates the period of a scheduled report.
   */
  public void updatePeriod(Session session, final Long aSchedulingTemplateId, final SchedulingPeriod period) {
    log.trace("Updating scheduling temporal params: {} to {}", aSchedulingTemplateId, period.toString());
    deleteTemporalCriteriaValueBySchedulingReportId(session, aSchedulingTemplateId);
    for (SchedulingCriteriaValue cts : period.asCriteriaValues()) {
      insertByCriteriaName(session, cts.getName(), cts.getValue(), aSchedulingTemplateId);
    }
  }

  /**
   * Update the functional criteria of a scheduled report.<br>
   * Deletes the old ones, replaces by the provided list of values.
   */
  public void updateFunctionalParameters(Session session, final Long aSchedulingTemplateId, final List<CriteriaValue> launchData) {
    if (log.isTraceEnabled()) {
      log.trace("Updating scheduling functional params: schedulingID={} to {}", aSchedulingTemplateId, Joiner.on(", ").join(launchData));
    }
    deleteFunctionalCriteriaValueBySchedulingReportId(session, aSchedulingTemplateId);
    for (CriteriaValue criteriaValue : launchData) {
      insertByCriteriaName(session, criteriaValue.getName(), criteriaValue.getValue(), aSchedulingTemplateId);
    }
  }

  /**
   * Delete all scheduling criteria value by scheduling report id (both functional and temporal).
   */
  private int deleteFunctionalCriteriaValueBySchedulingReportId(Session session, final Long schedulingReportId) {
    Query theQuery = session.createQuery(QUERY_DELETE_FUNCTIONAL_CRITERIAS_VALUES_OF_SCHEDULING);
    theQuery.setLong(KEY_REPORT_ID, schedulingReportId);
    return theQuery.executeUpdate();
  }

  /**
   * Delete all scheduling criteria value by scheduling report id (both functional and temporal).
   */
  private int deleteTemporalCriteriaValueBySchedulingReportId(Session session, final Long schedulingReportId) {
    Query theQuery = session.createQuery(QUERY_DELETE_TEMPORAL_CRITERIAS_VALUES_OF_SCHEDULING);
    theQuery.setLong(KEY_REPORT_ID, schedulingReportId);
    return theQuery.executeUpdate();
  }

  /**
   * Delete all scheduling criteria value by scheduling report id (both functional and temporal).
   */
  int deleteAllSchedulingCriteriaValueBySchedulingReportId(Session session, final Long schedulingReportId) {
    Query theQuery = session.createQuery(QUERY_DELETE_CRITERIAS_VALUES_OF_SCHEDULING);
    theQuery.setLong(KEY_REPORT_ID, schedulingReportId);
    return theQuery.executeUpdate();
  }

  /**
   * A Simple POJO class to bundle scheduling periodicity name - value pair.
   */
  @Getter
  public static class SchedulingCriteriaValue {

    private static final String NULL_STRING = "null";
    private final String name;
    private final String value;

    /**
     * Builds a SchedulingCriteria Name-Value object.
     *
     * @param name  if null, will be 'null'
     * @param value if null, will be 'null'
     */
    public SchedulingCriteriaValue(final String name, final String value) {
      this.name = name == null ? NULL_STRING : name;
      this.value = (value == null || value.isEmpty() || "null".equals(value)) ? NULL_STRING : value;
    }

    /**
     * Builds a SchedulingCriteria Name-Value object.
     *
     * @param criteria if null, will be 'null'
     * @param value    if null, will be 'null'
     */
    public SchedulingCriteriaValue(final SchedulingEnum criteria, final String value) {
      this.name = criteria == null ? NULL_STRING : criteria.toString();
      this.value = value == null ? NULL_STRING : value;
    }

    @Override
    public String toString() {
      return name + " = " + value;
    }
  }
}
