package de.ivu.elect.presentation.hilfe;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.benutzerverwaltung.boundary.PermissionService;
import de.ivu.elect.business.mandant.entity.MandantArt;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.jee.faces.security.AbstractSecurityViewValidator;
import de.ivu.elect.jee.startup.PostStartupAsync;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.ui.ViewIterator;


import static java.util.stream.Collectors.toList;

/**
 *  on 19.08.2015.
 */
public class OnlineHilfeIdMapPostStartup implements PostStartupAsync {

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

  public static final String HMCONTEXTIDS_JS = "hmcontextids.js";

  @Inject
  private transient ViewIterator viewIterator;

  @Inject
  private transient OnlineHilfeVerfuegbar onlineHilfeVerfuegbar;

  @Inject
  private transient OnlineHilfeIdMap onlineHilfeIdMap;

  @Inject
  private transient PermissionService permissionService;

  @Inject
  private transient HelpReader helpReader;

  @Override
  public void postStartup() {
    LOG.info("Lese Hilfe-Ids ein: " + onlineHilfeVerfuegbar.isOnlineHilfeVerfuegbar());

    if (onlineHilfeVerfuegbar.isOnlineHilfeVerfuegbar()) {

      Set<UseCasePermission> moeglicheRechte = bestimmeMoeglicheRechteDerAnwendung();
      Map<String, String> hilfeIdsFuerViews = new HashMap<>();
      Set<String> viewIdsMehrfachVergebeneIdOk = new HashSet<>();
      Set<String> viewIdsKoennenFehlen = new HashSet<>();

      // !!!!!!!!!!!
      // Falls beim Start der Anwendung hier eine Fehlermeldung erscheint, gibt es wahrscheinlich einen View, der ohne Scope annotiert ist und deshalb instantiert wird
      // !!!!!!!!!!!
      viewIterator.iterateViews(view -> {
        OnlineHilfeId onlineHilfeIdAnnotation = view.getClass().getAnnotation(OnlineHilfeId.class);
        View viewAnnotation = view.getClass().getAnnotation(View.class);

        if (viewAnnotation != null) {
          AbstractSecurityViewValidator.cache(viewAnnotation.viewId(), viewAnnotation); // Da wir eh alle ViewIds geladen haben, können wir diese gleich cachen

          // ELECT-5913 View nur pruefen, wenn Recht überhaupt möglich...
          if (UseCasePermission.OHNE.equals(viewAnnotation.useCase()) /*ELECT-6783*/ || moeglicheRechte.contains(viewAnnotation.useCase())) {

            String hilfeId = null;
            boolean isMehrfachOk = false;
            boolean isOkWennFehlend = false;
            if (onlineHilfeIdAnnotation != null) {
              hilfeId = onlineHilfeIdAnnotation.value();
              isMehrfachOk = onlineHilfeIdAnnotation.mehrfachVergebenOk();
              isOkWennFehlend = onlineHilfeIdAnnotation.okWennFehlend();
            }

            String viewId = viewAnnotation.viewId();

            if (StringUtils.isBlank(hilfeId)) {
              LOG.info("Keine Hilfe-ID für View: '" + viewId + "'");
            } else {
              if (!KeineOnlineHilfe.KEINE_ONLINE_HILFE.equalsIgnoreCase(hilfeId)) {
                hilfeIdsFuerViews.put(viewId, hilfeId);
                if (isMehrfachOk) {
                  viewIdsMehrfachVergebeneIdOk.add(viewId);
                }
                if (isOkWennFehlend) {
                  viewIdsKoennenFehlen.add(viewId);
                }
              }
            }
          }
        }
      });
      Set<String> fehlendeIds = pruefeAufExistenz(hilfeIdsFuerViews, viewIdsKoennenFehlen);
      pruefeAufDoppeltVerwendeteIDs(hilfeIdsFuerViews, viewIdsMehrfachVergebeneIdOk);

      Map<String, String> vorhandeneHilfeIdsFuerViews = hilfeIdsFuerViews.entrySet().stream()
          .filter(entry -> fehlendeIds != null && !fehlendeIds.contains(entry.getValue()))
          .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b)); // ELECT-6057

      onlineHilfeIdMap.setHilfeIdsFuerViewIds(vorhandeneHilfeIdsFuerViews);

    }

    LOG.info("Lese Hilfe-Ids ein - done");

  }

  private Set<UseCasePermission> bestimmeMoeglicheRechteDerAnwendung() {

    Set<UseCasePermission> result = new HashSet<>();
    for (MandantArt mandantArt : MandantArt.values()) {
      result.addAll(permissionService.getAvailablePermissionsForCurrentCustomer(mandantArt));
    }
    return result;
  }

  private void pruefeAufDoppeltVerwendeteIDs(Map<String, String> hilfeIdsFuerViews, Set<String> viewIdsMehrfachVergebeneIdOk) {
    LOG.info("Pruefe auf doppelt verwendete IDs");
    Map<String, List<Map.Entry<String, String>>> mappedNachId = hilfeIdsFuerViews.entrySet().stream()
        .collect(Collectors.groupingBy(Map.Entry::getValue, toList()));

    mappedNachId.entrySet().stream().filter(e -> e.getValue().size() > 1).sorted(Map.Entry.comparingByKey()).forEach(e ->
        e.getValue().stream().filter(ve -> !viewIdsMehrfachVergebeneIdOk.contains(ve.getKey()))
            .forEach(ve -> LOG.warn("ID: '" + e.getKey() + "' mehrfach vergeben, Thema oder @OnlineHilfeId korrigieren!! ViewId: " + ve.getKey())));
    LOG.info("Pruefe auf doppelt verwendete IDs - done");

  }

  @Nullable
  private Set<String> pruefeAufExistenz(Map<String, String> hilfeIdsFuerViews, Set<String> viewIdsKoennenFehlen) {
    // ELECT-4096
    LOG.info("Pruefe auf Existenz");

    byte[] bytes = helpReader.readContentFromHelp(HMCONTEXTIDS_JS);
    if (bytes == null) {
      LOG.error("Help and Manual Context-Datei nicht gefunden! '" + HMCONTEXTIDS_JS + "'");
      return null;
    }
    String hmContext = new String(bytes, StandardCharsets.UTF_8);

    if (StringUtils.isBlank(hmContext)) {
      LOG.error("Help and Manual Context-Datei ist leer! '" + HMCONTEXTIDS_JS + "'");
      return null;  // ausnahmsweise mal null...
    }

    Set<String> fehlendeIds = new HashSet<>();
    hilfeIdsFuerViews.entrySet().stream().filter(e -> !hmContext.contains("hmContextIds[\"" + e.getValue() + "\"]=")).sorted(
        Map.Entry.comparingByValue()).forEach(e ->
    {
      String kontextId = e.getValue();
      String viewId = e.getKey();
      if (!viewIdsKoennenFehlen.contains(viewId)) {
        fehlendeIds.add(kontextId);
        LOG.warn("Für Kontext ID: '" + kontextId + "' kein Eintrag in Help and Manual Context-Datei " + HMCONTEXTIDS_JS + ", ViewId: " + e.getKey());
      }
    });

    LOG.info("Pruefe auf Existenz - fertig, fehlend: " + fehlendeIds);
    return fehlendeIds;
  }

}
