
package de.ivu.elect.presentation.system;

import static java.util.Arrays.asList;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toSet;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.deltaspike.core.api.scope.ConversationGroup;
import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.chart.Axis;
import org.primefaces.model.chart.AxisType;
import org.primefaces.model.chart.CategoryAxis;
import org.primefaces.model.chart.ChartSeries;
import org.primefaces.model.chart.HorizontalBarChartModel;
import org.primefaces.model.chart.LineChartSeries;
import org.primefaces.model.chart.LinearAxis;

import de.ivu.elect.business.benutzerverwaltung.boundary.AngemeldeteBenutzer;
import de.ivu.elect.business.benutzerverwaltung.entity.AngemeldeterBenutzer;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.UseCaseSecure;
import de.ivu.elect.business.system.boundary.PerformanceStatistik;
import de.ivu.elect.business.system.boundary.PerformanceStatistikSearchParameters;
import de.ivu.elect.business.system.entity.PerformanceStatistikSessionType;
import de.ivu.elect.business.system.entity.PerformanceStatistikViewAccessType;
import de.ivu.elect.business.system.entity.PerformanceStatistikViewType;
import de.ivu.elect.business.system.entity.PerformanceStatistikViewZeitraum;
import de.ivu.elect.business.system.entity.PerformanceStatistikZeitDarstellung;
import de.ivu.elect.jee.faces.download.DownloadAction;
import de.ivu.elect.jee.performance.PerformanceStore;
import de.ivu.elect.jee.performance.PhaseIdEnum;
import de.ivu.elect.jee.performance.ViewAccessTargetStatistics;
import de.ivu.elect.jee.presentation.charts.AbstractChartModelBuilder;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.LambdaEnumProperty;
import de.ivu.elect.jee.presentation.dynamicform.LambdaStringSelectOneProperty;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyBooleanProperty;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyStringProperty;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.presentation.AbstractElectSearchView;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;
import de.ivu.elect.presentation.security.AbstractGebietSecurityFilter;

@OnlineHilfeId("707")
@RequestScoped
@Named(PerformanceStatistikView.COMPONENT_NAME)
@View(viewId = PerformanceStatistikView.VIEW_ID, useCase = UseCasePermission.PERFORMANCE_STATISTIK_SEARCH)
public class PerformanceStatistikView extends AbstractElectSearchView<ViewAccessTargetStatistics> {

  public static final String VIEW_ID = "/system/performance-statistik.xhtml";

  public static final String COMPONENT_NAME = "performanceStatistikView";

  private static final long serialVersionUID = 1L;

  private static final Map<PhaseIdEnum, String> COLOR_MAP;

  @Inject
  private transient DownloadAction downloadAction;

  @Inject
  @ConversationGroup(PerformanceStatistikSearchParameters.class)
  private transient PerformanceStatistikSearchParameters searchParameters;

  @Inject
  private transient AngemeldeteBenutzer angemeldeteBenutzer;

  @Inject
  private transient PerformanceStatistik performanceStatistik;

  @Inject
  @ConversationGroup(PerformanceStatistikSearchParameters.class)
  private transient PerformanceStatistikSnapshot performanceStatistikSnapshot;

  private HorizontalBarChartModel performanceModel;

  public PerformanceStatistikView() {
    super();
  }

  @Override
  protected String getViewId() {
    return VIEW_ID;
  }

  @Override
  protected AbstractGebietSecurityFilter<ViewAccessTargetStatistics> getGebietSecurityFilter() {
    return KEIN_GEBIET_SECURITY_FILTER;
  }

  @Override
  public boolean isSupportsLazyLoading() {
    return false; // ELECT-8409
  }

  @Override
  public PerformanceStatistikSearchParameters getSearchParameters() {
    return searchParameters;
  }

  /**
   * @deprecated To be removed...
   * @see AbstractElectSearchView#presetSearchParameters
   */
  @Deprecated
  @Override
  public void presetSearchParameters() {
    // To be removed...
  }

  public HorizontalBarChartModel getPerformanceModel() {
    if (performanceModel == null) {
      performanceModel = createPerformanceModel();
    }
    return performanceModel;
  }

  @Override
  public void resetSearchResult() {
    performanceModel = null;
    super.resetSearchResult();
  }

  @Nullable
  @UseCaseSecure(UseCasePermission.PERFORMANCE_STATISTIK_SEARCH)
  public String logStatistics() {
    String log = PerformanceStore.logStatistics();
    addInfoMessage("performance.statistics.log");
    DefaultStreamedContent.Builder streamContentBuilder = DefaultStreamedContent.builder()
        .stream(() -> new ByteArrayInputStream(log.getBytes(StandardCharsets.UTF_8)))
        .contentType("application/json")
        .name("performance.json")
        .contentEncoding(StandardCharsets.UTF_8.name());
    downloadAction.download(streamContentBuilder.build());
    return VIEW_ID;
  }

  @Nullable
  @UseCaseSecure(UseCasePermission.PERFORMANCE_STATISTIK_SEARCH)
  public String createSnapshot() {
    addInfoMessage("performance.statistics.snapshot");
    performanceStatistikSnapshot.snapshot();
    resetSearchParametersModel();
    resetSearchResult();
    getSearchParameters().setStatistikViewZeitraum(PerformanceStatistikViewZeitraum.SNAPSHOT);
    return VIEW_ID;
  }

  @Redirect
  @UseCaseSecure(UseCasePermission.PERFORMANCE_STATISTIK_SEARCH)
  public String aktiviereStatistik() {
    PerformanceStore.aktiviereStatistik();
    return VIEW_ID;
  }

  @Redirect
  @UseCaseSecure(UseCasePermission.PERFORMANCE_STATISTIK_SEARCH)
  public String deaktiviereStatistik() {
    PerformanceStore.deaktiviereStatistik();
    return VIEW_ID;
  }

  @Redirect
  @UseCaseSecure(UseCasePermission.PERFORMANCE_STATISTIK_SEARCH)
  public String aktiviereStatistikLogging() {
    PerformanceStore.aktiviereStatistikLogging();
    return VIEW_ID;
  }

  @Redirect
  @UseCaseSecure(UseCasePermission.PERFORMANCE_STATISTIK_SEARCH)
  public String deaktiviereStatistikLogging() {
    PerformanceStore.deaktiviereStatistikLogging();
    return VIEW_ID;
  }

  @Redirect
  @UseCaseSecure(UseCasePermission.PERFORMANCE_STATISTIK_SEARCH)
  public String resetStatistik() {
    PerformanceStore.resetStatistik();
    performanceStatistikSnapshot.reset();
    resetSearchResult();
    resetSearchParametersModel();
    getSearchParameters().setStatistikViewZeitraum(PerformanceStatistikViewZeitraum.SYSTEM_START);
    return VIEW_ID;
  }

  private HorizontalBarChartModel createPerformanceModel() {
    HorizontalBarChartModel result = new HorizontalBarChartModel();

    if (isStatistikAktiviert()) {

      Set<PhaseIdEnum> verwendetePhaseIds = getSearchResult().stream().map(ViewAccessTargetStatistics::getPhaseId).collect(toSet());

      Map<PhaseIdEnum, ChartSeries> seriesPerPhaseId = new EnumMap<>(PhaseIdEnum.class);

      List<PhaseIdEnum> reihenfolgeEnums = new ArrayList<>();
      for (PhaseIdEnum phaseIdEnum : PhaseIdEnum.values()) {
        if (verwendetePhaseIds.contains(phaseIdEnum)) {
          ChartSeries series = new ChartSeries();
          seriesPerPhaseId.put(phaseIdEnum, series);
          series.setLabel(messages.lokalisiere(phaseIdEnum));
          series.setXaxis(AxisType.X2);
          result.addSeries(series);
          reihenfolgeEnums.add(phaseIdEnum);
        }
      }

      LineChartSeries countSeries = new LineChartSeries();
      countSeries.setLabel(messages.getMessageForMessageKey("performance.statistics.series.count"));
      countSeries.setXaxis(AxisType.X);
      countSeries.setYaxis(AxisType.Y);
      countSeries.setDisableStack(true);
      countSeries.setShowLine(false);
      result.addSeries(countSeries);

      result.setSeriesColors(reihenfolgeEnums.stream().map(COLOR_MAP::get).collect(Collectors.joining(",")) + ", " + AbstractChartModelBuilder.DUNKEL_ROT);

      getStatisticsNachLabelGemappt().entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(e -> {
        String label = e.getKey();
        List<ViewAccessTargetStatistics> entries = e.getValue();
        Set<PhaseIdEnum> phaseIdPresent = entries.stream().map(ViewAccessTargetStatistics::getPhaseId).collect(toSet());

        long count = 0L;

        for (ViewAccessTargetStatistics entry : entries) {
          count = Math.max(count, entry.getCount());
          PhaseIdEnum phaseId = entry.getPhaseId();
          seriesPerPhaseId.get(phaseId).set(label, getSearchParameters().getZeitDarstellung().getValue(entry));
        }

        for (PhaseIdEnum verwendetePhaseId : verwendetePhaseIds) {
          if (!phaseIdPresent.contains(verwendetePhaseId)) {
            seriesPerPhaseId.get(verwendetePhaseId).set(label, 0L);
          }
        }

        countSeries.set(label, count);
      });
    }

    result.setLegendPosition("e");
    result.setStacked(true);

    Axis xAxis = result.getAxis(AxisType.X);
    xAxis.setLabel(messages.getMessageForMessageKey("performance.statistics.unit.count"));
    xAxis.setMin(0);

    Axis xAxis2 = new LinearAxis(messages.getMessageForMessageKey(getSearchParameters().getZeitDarstellung().getMsgKey()));
    xAxis2.setMin(0);
    result.getAxes().put(AxisType.X2, xAxis2);

    Axis yAxis = new CategoryAxis(messages.getMessageForMessageKey(getSearchParameters().getStatistikViewType().getMsgKey()));
    result.getAxes().put(AxisType.Y, yAxis);

    return result;
  }

  private Map<String, List<ViewAccessTargetStatistics>> getStatisticsNachLabelGemappt() {
    return getSearchResult().stream().collect(groupingBy(e -> e.getViewId() + " (" + messages.lokalisiere(e.getAccessType()) + ")"));
  }

  public String getHeightInPx() {
    if (isStatistikAktiviert()) {
      return Long.toString(Math.max(400, 35L * getStatisticsNachLabelGemappt().size()));
    }
    return "400";
  }

  @Override
  protected List<ViewAccessTargetStatistics> performSearch() {
    return performanceStatistik.search(getSearchParameters(), performanceStatistikSnapshot.getSnapshot());
  }

  @Override
  public void addProperties(FormBuilder formBuilder) {
    formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("performance.statistik.aktiviert"), isStatistikAktiviert()));
    formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("performance.statistik.logging.aktiviert"), isStatistikLoggingAktiviert()));

    if (performanceStatistikSnapshot.isSnapshotPresent()) {
      formBuilder.addProperty(new ReadOnlyStringProperty(getMessage("performance.statistik.snapshot.vorhanden"), userLocale.formatDateTime(performanceStatistikSnapshot.getTimestamp())));
    }

    List<PerformanceStatistikViewZeitraum> zeitraeume = bestimmeZeitraeume();
    formBuilder.addProperty(new LambdaEnumProperty<>(getMessage("performance.statistik.property.zeitraum"), true, getSearchParameters()::setStatistikViewZeitraum,
        getSearchParameters()::getStatistikViewZeitraum, PerformanceStatistikViewZeitraum.class, zeitraeume)).withReadonly(zeitraeume.size() < 2);

    formBuilder.addProperty(new LambdaEnumProperty<>(getMessage("performance.statistik.property.type"), true, getSearchParameters()::setStatistikViewType, getSearchParameters()::getStatistikViewType,
        PerformanceStatistikViewType.class, asList(PerformanceStatistikViewType.values())));

    formBuilder.addProperty(new LambdaEnumProperty<>(getMessage("performance.statistik.property.access.type"), true, getSearchParameters()::setStatistikViewAccessType,
        getSearchParameters()::getStatistikViewAccessType, PerformanceStatistikViewAccessType.class, asList(PerformanceStatistikViewAccessType.values())));

    formBuilder.addProperty(new LambdaEnumProperty<>(getMessage("performance.statistik.property.zeit.darstellung"), true, getSearchParameters()::setZeitDarstellung,
        getSearchParameters()::getZeitDarstellung, PerformanceStatistikZeitDarstellung.class, asList(PerformanceStatistikZeitDarstellung.values())));

    formBuilder.addProperty(new LambdaEnumProperty<>(getMessage("performance.statistik.property.session.type"), true, getSearchParameters()::setStatistikSessionType,
        getSearchParameters()::getStatistikSessionType, PerformanceStatistikSessionType.class, asList(PerformanceStatistikSessionType.values())));

    formBuilder.addProperty(
        new LambdaStringSelectOneProperty(getMessage("performance.statistik.property.user"), false, getSearchParameters()::setNutzer, getSearchParameters()::getNutzer, bestimmeNutzer(), true));

  }

  @Nonnull
  public List<PerformanceStatistikViewZeitraum> bestimmeZeitraeume() {
    ArrayList<PerformanceStatistikViewZeitraum> result = new ArrayList<>(asList(PerformanceStatistikViewZeitraum.values()));
    if (!performanceStatistikSnapshot.isSnapshotPresent()) {
      result.remove(PerformanceStatistikViewZeitraum.SNAPSHOT);
    }
    return result;
  }

  private List<String> bestimmeNutzer() {
    boolean alleNutzerSehen = hasPermission(UseCasePermission.ANGEMELDETE_BENUTZER_SEHEN);
    return angemeldeteBenutzer.getAngemeldeteBenutzerNoPermissionCheck().stream()
        .filter(angemeldeterBenutzer -> alleNutzerSehen || getPrincipal().getBenutzerKennung().equals(angemeldeterBenutzer.getBenutzerkennung())).map(AngemeldeterBenutzer::getNameSessionIdGui)
        .sorted(String::compareTo).collect(Collectors.toList());

  }

  public boolean isStatistikAktiviert() {
    return performanceStatistik.isStatistikAktiviert();
  }

  public boolean isStatistikLoggingAktiviert() {
    return performanceStatistik.isStatistikLoggingAktiviert();
  }

  static {
    COLOR_MAP = new EnumMap<>(PhaseIdEnum.class);
    COLOR_MAP.put(PhaseIdEnum.APPLY_REQUEST_VALUES, AbstractChartModelBuilder.GRAU);
    COLOR_MAP.put(PhaseIdEnum.INVOKE_APPLICATION, AbstractChartModelBuilder.ORANGE);
    COLOR_MAP.put(PhaseIdEnum.RESTORE_VIEW, AbstractChartModelBuilder.PINK);
    COLOR_MAP.put(PhaseIdEnum.PROCESS_VALIDATIONS, AbstractChartModelBuilder.GRUEN);
    COLOR_MAP.put(PhaseIdEnum.UPDATE_MODEL_VALUES, AbstractChartModelBuilder.CYAN);
    COLOR_MAP.put(PhaseIdEnum.RENDER_RESPONSE, AbstractChartModelBuilder.BLAU);
  }
}
