package de.ivu.elect.business.system.boundary;

import de.ivu.elect.business.system.entity.*;
import de.ivu.elect.util.CaseAndDiacriticIgnoringComparator;
import org.hibernate.ejb.HibernateEntityManagerFactory;
import org.hibernate.stat.SecondLevelCacheStatistics;
import org.hibernate.stat.Statistics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.enterprise.inject.Specializes;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

@Specializes
public class ActivatedHibernateStatistik extends HibernateStatistik {
  private static final long serialVersionUID = 1L;

  private static final Logger LOG = LoggerFactory.getLogger(HibernateStatistik.class);

  public static final Comparator<String> STRING_COMPARATOR = new CaseAndDiacriticIgnoringComparator(Locale.ENGLISH);

  @PersistenceUnit
  transient EntityManagerFactory entityManagerFactory;

  @Override
  public boolean isStatistikVerfuegbar() {
    return getStatistics() != null;
  }

  @Override
  public boolean isStatistikAktiviert() {
    if (isStatistikVerfuegbar()) {
      return getStatistics().isStatisticsEnabled();
    }

    return false;
  }

  @Override
  public void aktiviereStatistik() {
    if (isStatistikVerfuegbar()) {
      if (!isStatistikAktiviert()) {
        getStatistics().setStatisticsEnabled(true);
        LOG.info("Hibernate-Statistiken sind jetzt aktiviert");
      }
    } else {
      throw new IllegalStateException("Hibernate-Statistiken nicht verfügbar!");
    }
  }

  @Override
  public void deaktiviereStatistik() {
    if (isStatistikAktiviert()) {
      getStatistics().setStatisticsEnabled(false);
      LOG.info("Hibernate-Statistiken sind jetzt deaktiviert");
    }
  }

  @Override
  public void resetStatistik() {
    if (isStatistikAktiviert()) {
      getStatistics().clear();
      LOG.info("Hibernate-Statistiken zurückgesetzt");
    }
  }

  @Override
  public List<HibernateEntityStatistik> getEntityStatistiken() {
    if (isStatistikAktiviert()) {
      List<HibernateEntityStatistik> result = new ArrayList<>();

      for (String entityName : getStatistics().getEntityNames()) {
        result.add(new ActivatedHibernateEntityStatistik(filterEntityName(entityName), getStatistics().getEntityStatistics(entityName)));
      }

      result = result.stream().filter(HibernateEntityStatistik::isWertVorhanden).collect(Collectors.toList());

      Collections.sort(result, (u, v) -> STRING_COMPARATOR.compare(u.getName(), v.getName()));

      return result;
    }

    return Collections.emptyList();
  }

  public List<HibernateQueryStatistik> getQueryStatistiken() {
    if (isStatistikAktiviert()) {
      List<HibernateQueryStatistik> result = new ArrayList<>();

      for (String queryName : getStatistics().getQueries()) {
        HibernateQueryStatistik queryStatistik = new ActivatedHibernateQueryStatistik(queryName,
                getStatistics().getQueryStatistics(queryName));

        if (queryStatistik.isValid()) {
          result.add(queryStatistik);
        }
      }

      Collections.sort(result, (u, v) -> STRING_COMPARATOR.compare(u.getName(), v.getName()));

      return result;
    }

    return Collections.emptyList();
  }

  @Override
  public List<HibernateSecondLevelCacheStatistik> getSecondLevelCacheStatistiken() {
    if (isStatistikAktiviert()) {
      List<HibernateSecondLevelCacheStatistik> result = new ArrayList<>();

      Statistics statistics = getStatistics();
      final long hitCount = statistics.getSecondLevelCacheHitCount();
      final long missCount = statistics.getSecondLevelCacheMissCount();
      final long putCount = statistics.getSecondLevelCachePutCount();

      for (String region : statistics.getSecondLevelCacheRegionNames()) {
        result.add(new ActivatedHibernateSecondLevelCacheStatistik(filterEntityName(region), statistics.getSecondLevelCacheStatistics(region)));
      }

      result = result.stream().filter(HibernateSecondLevelCacheStatistik::isWertVorhanden).collect(Collectors.toList());

      Collections.sort(result, (u, v) -> STRING_COMPARATOR.compare(u.getName(), v.getName()));

      result.add(0, new ActivatedHibernateSecondLevelCacheStatistik("<GLOBAL>", new SecondLevelCacheStatistics() {
        @Override
        public long getHitCount() {
          return hitCount;
        }

        @Override
        public long getMissCount() {
          return missCount;
        }

        @Override
        public long getPutCount() {
          return putCount;
        }

        @Override
        public long getElementCountInMemory() {
          return 0;
        }

        @Override
        public long getElementCountOnDisk() {
          return 0;
        }

        @Override
        public long getSizeInMemory() {
          return 0;
        }

        @Override
        public Map getEntries() {
          return new HashMap();
        }
      }));

      return result;
    }

    return Collections.emptyList();
  }

  @Override
  public List<HibernateCollectionStatistik> getCollectionStatistiken() {
    if (isStatistikAktiviert()) {
      List<HibernateCollectionStatistik> result = new ArrayList<>();

      for (String collectionRoleName : getStatistics().getCollectionRoleNames()) {
        result.add(new ActivatedHibernateCollectionStatistik(filterEntityCollectionName(collectionRoleName),
                getStatistics().getCollectionStatistics(collectionRoleName)));
      }

      result = result.stream().filter(HibernateCollectionStatistik::isWertVorhanden).collect(Collectors.toList());

      result.sort((u, v) -> STRING_COMPARATOR.compare(u.getName(), v.getName()));

      return result;
    }

    return Collections.emptyList();
  }

  /**
   * Package-Kram abschneiden
   */
  private String filterEntityName(String entityName) {

    if (entityName == null || entityName.indexOf('.') < 0) {
      return entityName;
    }
    return entityName.substring(entityName.lastIndexOf('.') + 1);
  }

  /**
   * Package-Kram abschneiden
   */
  private String filterEntityCollectionName(String collectionRoleName) {
    if (collectionRoleName == null || collectionRoleName.indexOf('.') < 0) {
      return collectionRoleName;
    }

    String[] splitted = collectionRoleName.split("\\.");
    if (splitted.length < 2) {
      return collectionRoleName;
    }
    return splitted[splitted.length - 2] + "." + splitted[splitted.length - 1];
  }

  protected Statistics getStatistics() {
    try {
      // ELECT-9463 Per Reflection, da sich Hibernate-Klassen je nach Version ständig in anderen Packages wiederfinden
      Method getSessionFactory = entityManagerFactory.getClass().getMethod("getSessionFactory");
      Object sessionFactory = getSessionFactory.invoke(entityManagerFactory);
      Method getStatistics = sessionFactory.getClass().getMethod("getStatistics");
      Statistics statistics = (Statistics) getStatistics.invoke(sessionFactory);
      if (statistics != null) {
        return statistics;
      }
    } catch (Exception | Error e) {
      LOG.warn("Hibernate Reflection Statistiken nicht verfügbar: " + e.getMessage());
    }

    return getStatisticsHibernate5Manuell();
  }

  protected Statistics getStatisticsHibernate5Manuell() {
    try {
      if (entityManagerFactory instanceof org.hibernate.jpa.internal.EntityManagerFactoryImpl) {
        return ((org.hibernate.jpa.internal.EntityManagerFactoryImpl) entityManagerFactory).getSessionFactory().getStatistics();
      }
    } catch (Exception | Error e) { // NoSuchMethodError, weil bei WF10 ein Error geworfen wird  //ELECT-4163 Hibernate Statistiken für WildFly 10 aktivieren
      LOG.warn("Hibernate 5 Statistiken nicht verfügbar: " + e.getMessage());
    }

    return getStatisticsHibernate4();
  }

  // fallback auf hibernate 4
  private Statistics getStatisticsHibernate4() {
    try {

      if (entityManagerFactory instanceof org.hibernate.ejb.HibernateEntityManagerFactory) {
        return ((HibernateEntityManagerFactory) entityManagerFactory).getSessionFactory().getStatistics();
      }
    } catch (Exception | Error e) { // NoSuchMethodError, kann bei älteren WildFly-Versionen passieren...
      LOG.warn("Hibernate 4 Statistiken nicht verfügbar: " + e.getMessage());
    }
    return null;
  }

  @Override
  public String logStatistics(boolean vollstaendig, String lineSeparator) {
    StringBuilder sb = new StringBuilder();
    if (isStatistikVerfuegbar()) {
      if (vollstaendig) {
        getStatistics().logSummary();
      }

      sb.append("QueryStatistiken:").append(lineSeparator);
      sb.append(HibernateQueryStatistik.toStringHeader());
      sb.append(lineSeparator);
      for (HibernateQueryStatistik hibernateQueryStatistik : getQueryStatistiken()) {
        sb.append(hibernateQueryStatistik.toString());
        sb.append(lineSeparator);
      }

      sb.append("EntityStatistiken:" + lineSeparator);
      sb.append(HibernateEntityStatistik.toStringHeader());
      sb.append(lineSeparator);
      for (HibernateEntityStatistik hibernateEntityStatistik : getEntityStatistiken()) {
        sb.append(hibernateEntityStatistik.toString());
        sb.append(lineSeparator);
      }

      sb.append("SecondLevelCacheStatistiken:" + lineSeparator);
      sb.append(HibernateSecondLevelCacheStatistik.toStringHeader());
      sb.append(lineSeparator);
      for (HibernateSecondLevelCacheStatistik hibernateSecondLevelCacheStatistik : getSecondLevelCacheStatistiken()) {
        sb.append(hibernateSecondLevelCacheStatistik.toString());
        sb.append(lineSeparator);
      }

      sb.append("CollectionStatistiken:" + lineSeparator);
      sb.append(HibernateCollectionStatistik.toStringHeader());
      sb.append(lineSeparator);
      for (HibernateCollectionStatistik hibernateCollectionStatistik : getCollectionStatistiken()) {
        sb.append(hibernateCollectionStatistik.toString());
        sb.append(lineSeparator);
      }

      sb.append("HibernateStatistikEntries:" + lineSeparator);
      sb.append(HibernateStatistikEntry.toStringHeader());
      sb.append(lineSeparator);
      for (HibernateStatistikEntry hibernateStatistikEntry : getStatistikEntries()) {
        sb.append(hibernateStatistikEntry.toString());
        sb.append(lineSeparator);
      }
      LOG.info(lineSeparator + sb.toString());
    } else {
      LOG.info("Hibernate-Statistiken sind deaktiviert");
    }
    return sb.toString();
  }

  @Override
  public List<HibernateStatistikEntry> getStatistikEntries() {

    List<HibernateStatistikEntry> result = new ArrayList<>();
    if (isStatistikVerfuegbar()) {
      Statistics s = getStatistics();

      result.add(new HibernateStatistikEntry("EntityDeleteCount", s.getEntityDeleteCount()));

      result.add(new HibernateStatistikEntry("EntityInsertCount", s.getEntityInsertCount()));

      result.add(new HibernateStatistikEntry("EntityLoadCount", s.getEntityLoadCount()));

      result.add(new HibernateStatistikEntry("EntityFetchCount", s.getEntityFetchCount()));

      result.add(new HibernateStatistikEntry("EntityUpdateCount", s.getEntityUpdateCount()));

      result.add(new HibernateStatistikEntry("QueryExecutionCount", s.getQueryExecutionCount()));

      result.add(new HibernateStatistikEntry("QueryExecutionMaxTime", s.getQueryExecutionMaxTime()));

      result.add(new HibernateStatistikEntry("QueryCacheHitCount", s.getQueryCacheHitCount()));

      result.add(new HibernateStatistikEntry("QueryCacheMissCount", s.getQueryCacheMissCount()));

      result.add(new HibernateStatistikEntry("QueryCachePutCount", s.getQueryCachePutCount()));

      result.add(new HibernateStatistikEntry("NaturalIdQueryExecutionCount", s.getNaturalIdQueryExecutionCount()));

      result.add(new HibernateStatistikEntry("NaturalIdQueryExecutionMaxTime", s.getNaturalIdQueryExecutionMaxTime()));

      result.add(new HibernateStatistikEntry("NaturalIdCacheHitCount", s.getNaturalIdCacheHitCount()));

      result.add(new HibernateStatistikEntry("NaturalIdCacheMissCount", s.getNaturalIdCacheMissCount()));

      result.add(new HibernateStatistikEntry("NaturalIdCachePutCount", s.getNaturalIdCachePutCount()));

      result.add(new HibernateStatistikEntry("UpdateTimestampsCacheHitCount", s.getUpdateTimestampsCacheHitCount()));

      result.add(new HibernateStatistikEntry("UpdateTimestampsCacheMissCount", s.getUpdateTimestampsCacheMissCount()));

      result.add(new HibernateStatistikEntry("UpdateTimestampsCachePutCount", s.getUpdateTimestampsCachePutCount()));

      result.add(new HibernateStatistikEntry("FlushCount", s.getFlushCount()));

      result.add(new HibernateStatistikEntry("ConnectCount", s.getConnectCount()));

      result.add(new HibernateStatistikEntry("SecondLevelCacheHitCount", s.getSecondLevelCacheHitCount()));

      result.add(new HibernateStatistikEntry("SecondLevelCacheMissCount", s.getSecondLevelCacheMissCount()));

      result.add(new HibernateStatistikEntry("SecondLevelCachePutCount", s.getSecondLevelCachePutCount()));

      result.add(new HibernateStatistikEntry("SessionCloseCount", s.getSessionCloseCount()));

      result.add(new HibernateStatistikEntry("SessionOpenCount", s.getSessionOpenCount()));

      result.add(new HibernateStatistikEntry("CollectionLoadCount", s.getCollectionLoadCount()));

      result.add(new HibernateStatistikEntry("CollectionFetchCount", s.getCollectionFetchCount()));

      result.add(new HibernateStatistikEntry("CollectionUpdateCount", s.getCollectionUpdateCount()));

      result.add(new HibernateStatistikEntry("CollectionRemoveCount", s.getCollectionRemoveCount()));

      result.add(new HibernateStatistikEntry("CollectionRecreateCount", s.getCollectionRecreateCount()));

      result.add(new HibernateStatistikEntry("StartTime", s.getStartTime()));

      result.add(new HibernateStatistikEntry("SuccessfulTransactionCount", s.getSuccessfulTransactionCount()));

      result.add(new HibernateStatistikEntry("TransactionCount", s.getTransactionCount()));

      result.add(new HibernateStatistikEntry("PrepareStatementCount", s.getPrepareStatementCount()));

      result.add(new HibernateStatistikEntry("CloseStatementCount", s.getCloseStatementCount()));

      result.add(new HibernateStatistikEntry("OptimisticFailureCount", s.getOptimisticFailureCount()));

    }

    return result;

  }
}
