package com.capgemini.reports.database.stats;

import com.capgemini.framework.common.DateHelper;
import com.capgemini.reports.ReportEnum;
import com.capgemini.reports.database.HibernateUtil;
import com.capgemini.reports.database.manager.MngReport;
import com.capgemini.reports.database.manager.MngReport.ReportLaunchByUser;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

import java.text.ParseException;
import java.util.*;
import java.util.Map.Entry;

/**
 * Class for dynamic content of JSP statistics page.
 **/
public final class GenJspStats {

  private static final String PLACEHOLDER_NUM = "0";
  private static final long MILLIS_IN_S = 1000L;
  private static final String START_PERIOD = "startPeriod";
  private static final String END_PERIOD = "endPeriod";
  private static final String TOTAL = "total";

  /**
   * Count exectutions.
   */
  private static final String HQL_COUNT_EXECUTIONS_QUERY;
  /**
   * Count TECHNICAL errors.
   */
  private static final String HQL_COUNT_TECHNICAL_ERRORS_QUERY;
  /**
   * Count FUNCTIONAL errors.
   */
  private static final String HQL_COUNT_FUNCTIONAL_ERRORS_QUERY;
  /**
   * Calc min, avg, and max duration of each report types.
   */
  private static final String HQL_MIN_MAX_AVG_DURATION_QUERY;

  static {
    String selectCount = "select T0.reportCode, count(DISTINCT T1.reportId) ";
    String selectStats =
      "select T0.reportCode, min(T0.generationDuration), avg(T0.generationDuration), max(T0.generationDuration) ";
    String from = "from DbReport T0, DbReportOwner T1 ";
    String whereCoherence = "where T0.id = T1.reportId ";
    String andPeriod = "and T1.requestDate between TO_DATE(:" + START_PERIOD + ") and TO_DATE(:" + END_PERIOD + ") + 1 ";
    String andSuccess = "and T0.endGenerationDate is not null and T0.state != 'FAILED' ";
    String andErrorTypeTech = "and T0.errorType = 'TECHNICAL' ";
    String andErrorTypeFunc = "and T0.errorType = 'FUNCTIONAL' ";
    String groupBy = "group by T0.reportCode";
    HQL_COUNT_EXECUTIONS_QUERY = selectCount + from + whereCoherence + andPeriod + groupBy;
    HQL_COUNT_TECHNICAL_ERRORS_QUERY = selectCount + from + whereCoherence + andPeriod + andErrorTypeTech + groupBy;
    HQL_COUNT_FUNCTIONAL_ERRORS_QUERY = selectCount + from + whereCoherence + andPeriod + andErrorTypeFunc + groupBy;
    HQL_MIN_MAX_AVG_DURATION_QUERY = selectStats + from + whereCoherence + andPeriod + andSuccess + groupBy;
  }

  private GenJspStats() {
    // Nothing to do...
  }

  /**
   * Generate the content of the Javascript array for all statistics.
   */
  public static String generateTimingsTableAsJS(final String aStartPeriod, final String aEndPeriod) throws ParseException {
    StringBuilder theJsCode = new StringBuilder();
    Collection<ReportStatBean> theReportStatList = getReportStatList(aStartPeriod, aEndPeriod);

    int theLineCount = 0;
    for (ReportStatBean currentStat : theReportStatList) {
      theJsCode.append(currentStat.toJs("reportStatArray[" + theLineCount + "]"));
      theLineCount++;
    }

    return theJsCode.toString();
  }

  /**
   * Compute statistics from database.
   */
  public static Collection<ReportStatBean> getReportStatList(final String startPeriod, final String endPeriod) throws ParseException {
    // Compute the limit date in the past for the analyzed interval
    Map<String, ReportStatBean> statMap = initMap();
    Date parsedStartPeriod = DateHelper.parseDate(startPeriod);
    Date parsedEndPeriod = DateHelper.parseDate(endPeriod);
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      countExecutions(parsedStartPeriod, parsedEndPeriod, session, statMap);
      countFunctionalErrors(parsedStartPeriod, parsedEndPeriod, session, statMap);
      countTechnicalErrors(parsedStartPeriod, parsedEndPeriod, session, statMap);
      calcMinMaxAvgDuration(parsedStartPeriod, parsedEndPeriod, session, statMap);
      return statMap.values();
    }
    finally {
      session.close();
    }
  }

  private static Map<String, ReportStatBean> initMap() {
    Map<String, ReportStatBean> statMap = new TreeMap<>();
    for (String reportCode : ReportEnum.getListReportCode()) {
      statMap.put(reportCode, new ReportStatBean(reportCode));
    }
    return statMap;
  }

  private static void countExecutions(final Date startPeriod, final Date endPeriod, final Session hibernateSession,
                                      final Map<String, ReportStatBean> theStatMap) {
    // Fill execution count
    Query query = hibernateSession.createQuery(HQL_COUNT_EXECUTIONS_QUERY);
    query.setDate(START_PERIOD, startPeriod);
    query.setDate(END_PERIOD, endPeriod);
    List<Object[]> theResultSet = query.list();
    for (Object[] currentRecord : theResultSet) {
      String reportCode = (String) currentRecord[0];
      Long reportCount = (Long) currentRecord[1];

      ReportStatBean theReportStatBean = theStatMap.get(reportCode);
      theReportStatBean.setExecutionCount(reportCount);
    }
  }

  private static void countFunctionalErrors(final Date aStartPeriod, final Date aEndPeriod, final Session hibernateSession,
                                            final Map<String, ReportStatBean> theStatMap) {
    // Fill functional error count
    Query theQuery = hibernateSession.createQuery(HQL_COUNT_FUNCTIONAL_ERRORS_QUERY);
    theQuery.setDate(START_PERIOD, aStartPeriod);
    theQuery.setDate(END_PERIOD, aEndPeriod);
    List<Object[]> theResultSet = theQuery.list();
    for (Object[] currentRecord : theResultSet) {
      String theReportCode = (String) currentRecord[0];
      Long theReportCount = (Long) currentRecord[1];

      ReportStatBean theReportStatBean = theStatMap.get(theReportCode);
      theReportStatBean.setFailedFuncCount(theReportCount);
    }
  }

  private static void countTechnicalErrors(final Date aStartPeriod, final Date aEndPeriod, final Session hibernateSession,
                                           final Map<String, ReportStatBean> theStatMap) {
    // Fill technical error count
    Query theQuery = hibernateSession.createQuery(HQL_COUNT_TECHNICAL_ERRORS_QUERY);
    theQuery.setDate(START_PERIOD, aStartPeriod);
    theQuery.setDate(END_PERIOD, aEndPeriod);
    List<Object[]> theResultSet = theQuery.list();
    for (Object[] currentRecord : theResultSet) {
      String theReportCode = (String) currentRecord[0];
      Long theReportCount = (Long) currentRecord[1];

      ReportStatBean theReportStatBean = theStatMap.get(theReportCode);
      theReportStatBean.setFailedTechCount(theReportCount);
    }
  }

  private static void calcMinMaxAvgDuration(final Date startPeriod, final Date endPeriod, final Session hibernateSession,
                                            final Map<String, ReportStatBean> statMap) {
    // Fill min, max and average durations
    Query theQuery = hibernateSession.createQuery(HQL_MIN_MAX_AVG_DURATION_QUERY);
    theQuery.setDate(START_PERIOD, startPeriod);
    theQuery.setDate(END_PERIOD, endPeriod);
    List<Object[]> theResultSet = theQuery.list();
    for (Object[] currentRecord : theResultSet) {
      String theReportCode = (String) currentRecord[0];
      ReportStatBean theReportStatBean = statMap.get(theReportCode);

      Long theReportMin = (Long) currentRecord[1];
      if (theReportMin != null) {
        theReportStatBean.setMinDuration(theReportMin / MILLIS_IN_S);
      }

      Double theReportAvg = (Double) currentRecord[2];
      if (theReportAvg != null) {
        Long theAvgRepDuration = theReportAvg.longValue();
        theReportStatBean.setAvgDuration(theAvgRepDuration / MILLIS_IN_S);
      }

      Long theReportMax = (Long) currentRecord[3];
      if (theReportMax != null) {
        theReportStatBean.setMaxDuration(theReportMax / MILLIS_IN_S);
      }
    }
  }


  private static List<UserStatBean> generateUserStatBean(Session session, final String startPeriod, final String endPeriod) throws ParseException {
    // ref list contains all report
    List<String> refListContainsAllReportCode = ReportEnum.getListReportCode();

    // Get all record
    Date parsedStartPeriod = DateHelper.parseDate(startPeriod);
    Date parsedEndPeriod = DateHelper.parseDate(endPeriod);
    List<ReportLaunchByUser> userReportList = new MngReport().searchUsersReports(session, parsedStartPeriod, parsedEndPeriod);
    userReportList.addAll(userReportList.size(), new MngReport().searchAdminSchedulingReports(session, parsedStartPeriod, parsedEndPeriod));

    Map<String, Map<String, Long>> mapObjects = new TreeMap<>();
    // objects of period
    for (ReportLaunchByUser curReportLaunchByUser : userReportList) {
      // current user name
      String currentUserName = curReportLaunchByUser.getSurname().concat(" ").concat(curReportLaunchByUser.getFirstName());

      // if username most exist
      if (mapObjects.containsKey(currentUserName)) {
        // if you don't have this current reportCode then put it
        if (!mapObjects.get(currentUserName).containsKey(curReportLaunchByUser.getReportCode())) {
          mapObjects.get(currentUserName).put(curReportLaunchByUser.getReportCode(), curReportLaunchByUser.getReportCount());

          // Then if total key exist so re-compute
          if (mapObjects.get(currentUserName).containsKey(TOTAL)) {
            Long curTotal = mapObjects.get(currentUserName).get(TOTAL);
            curTotal += curReportLaunchByUser.getReportCount();
            mapObjects.get(currentUserName).put(TOTAL, curTotal);
          }
        }
      }
      else { // then create the currentName and put reportCode and reportCount
        Map<String, Long> mapDatas = new TreeMap<>();
        mapDatas.put(curReportLaunchByUser.getReportCode(), curReportLaunchByUser.getReportCount());
        mapDatas.put(TOTAL, curReportLaunchByUser.getReportCount());
        mapObjects.put(currentUserName, mapDatas);
      }
    }

    // Generate object UserStaBean
    List<UserStatBean> usersListStatBean = new ArrayList<>();
    for (Entry<String, Map<String, Long>> userEntry : mapObjects.entrySet()) {
      // temp list for diff
      List<String> curListReportCode = new ArrayList<>();
      // create userStatBean
      String userName = userEntry.getKey();
      Map<String, Long> datas = userEntry.getValue();
      UserStatBean userStatBean = new UserStatBean(userName);
      for (Entry<String, Long> userEntryValues : datas.entrySet()) {
        userStatBean.getDatas().put(userEntryValues.getKey(), userEntryValues.getValue().toString());
        curListReportCode.add(userEntryValues.getKey());
      }
      // diff
      // findAll ref list report
      List<String> tempListContainsAllReport = new ArrayList<>(refListContainsAllReportCode);
      // diff findAll missing report code
      tempListContainsAllReport.removeAll(curListReportCode);

      for (String reportCodeMissing : tempListContainsAllReport) {
        userStatBean.getDatas().put(reportCodeMissing, PLACEHOLDER_NUM);
      }
      // add to list
      usersListStatBean.add(userStatBean);
    }

    return usersListStatBean;
  }

  /**
   * Second table implementation for report count per code & user login.
   **/

  public static String generateLaunchCountTableAsJS(final String aStartPeriod, final String aEndPeriod) throws ParseException {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      StringBuilder theJsCode = new StringBuilder();
      List<UserStatBean> users = generateUserStatBean(session, aStartPeriod, aEndPeriod);
      int theLineCount = 0;
      for (UserStatBean userStatBean : users) {
        theJsCode.append(userStatBean.toJs("userStatArray[" + theLineCount + "]"));
        theLineCount++;
      }
      return theJsCode.toString();
    }
    finally {
      session.close();
    }
  }

  /**
   * Generate a list of all users stats for csv
   */
  public static List<UserStatBean> generateLaunchCountTableForCsv(final String aStartPeriod, final String aEndPeriod) throws ParseException {
    SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
    Session session = sessionFactory.openSession();
    try {
      return generateUserStatBean(session, aStartPeriod, aEndPeriod);
    }
    finally {
      session.close();
    }
  }
}
