package de.ivu.elect.business.internet.control;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.datei.entity.DateiExportPK;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisDAO;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Praesentationsexportart;
import de.ivu.elect.business.ergebnis.entity.Praesentationsexportausloeser;
import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.boundary.AbstractWahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.internet.boundary.InternetPraesentationErmittler;
import de.ivu.elect.business.internet.boundary.PraesentationExporter;
import de.ivu.elect.business.internet.boundary.PraesentationsexportDAO;
import de.ivu.elect.business.internet.entity.Praesentationsexport;
import de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte;
import de.ivu.elect.business.konfiguration.entity.KonfigurationLoader;
import de.ivu.elect.business.mandant.boundary.MandantDAO;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.mandant.entity.MandantPK;
import de.ivu.elect.business.praesentation.boundary.PraesentationStatistikStore;
import de.ivu.elect.business.praesentation.entity.PraesentationAutomatischenExportAnstossen;
import de.ivu.elect.business.praesentation.entity.PraesentationBereitstellen;
import de.ivu.elect.business.praesentation.entity.PraesentationStatistik;
import de.ivu.elect.business.praesentation.entity.PraesentationUploadArt;
import de.ivu.elect.business.wahl.boundary.WahldurchfuehrungDAO;
import de.ivu.elect.business.wahl.entity.WahlPK;
import de.ivu.elect.business.wahl.entity.WahlParameterLoader;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;

@Stateless
public class PresentationGenerieren {

  protected static final Logger LOG = LoggerFactory.getLogger(PresentationGenerieren.class);

  protected static final Praesentationsexportausloeser PRAESENTATIONSEXPORTAUSLOESER = Praesentationsexportausloeser.SYSTEM;

  private static final Map<WahlPK, AtomicLong> ANZAHL_LAUFENDE_GENERIERUNGEN_PRO_WAHL = new ConcurrentHashMap<>();

  private static final long MAX_PRAESENTATION_PER_TRANSACTION = 1;

  @Inject
  protected transient ErgebnisDAO ergebnisDAO;

  @Inject
  protected transient PraesentationsexportDAO praesentationsexportDAO;

  @Inject
  protected transient PraesentationExporter praesentationExporter;

  @Inject
  protected transient WahldurchfuehrungDAO wahldurchfuehrungDAO;

  @Inject
  protected transient KonfigurationLoader konfigurationLoader;

  @Inject
  protected transient PresentationGenerierenEnabled presentationGenerierenEnabled;

  @Inject
  private transient PraesentationStatistikStore praesentationStatistikStore;

  @Inject
  private transient InternetPraesentationErmittler internetPraesentationErmittler;

  @Inject
  private transient WahlParameterLoader wahlParameterLoader;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  private transient MandantDAO mandantDAO;

  @Inject
  private transient PraesentationBereitstellenService praesentationBereitstellenService;


  public PresentationGenerieren() {
    super();
  }

  public void erzeugePraesentation(MandantPK mandantPK) {
    if (isDisabled() || !isExportAktiviert()) {
      return;
    }

    for (PresentationGenerierenZuExportierendesErgebnis presentationGenerierenZuExportierendesErgebnis : getNaechstesZuExportierendesErgebnis(mandantDAO.findByPrimaryKey(mandantPK), PRAESENTATIONSEXPORTAUSLOESER, MAX_PRAESENTATION_PER_TRANSACTION)) {
      LOG.info("Exportiere Präsentation für: " + presentationGenerierenZuExportierendesErgebnis);
      erzeugePraesentationFuerErgebnis(presentationGenerierenZuExportierendesErgebnis);
    }
  }

  public void erzeugePraesentationFuerErgebnis(PresentationGenerierenZuExportierendesErgebnis zuExportieren) {
    if (isDisabled()) {
      return;
    }
    Ergebnis ergebnis = zuExportieren.getErgebnis();

    AtomicLong zaehler = ANZAHL_LAUFENDE_GENERIERUNGEN_PRO_WAHL.computeIfAbsent(ergebnis.getWahl().getPk(), x -> new AtomicLong(0L));
    zaehler.incrementAndGet();
    try {

      Wahldurchfuehrung wahldurchfuehrung = wahldurchfuehrungDAO.findByPrimaryKey(ergebnis.getWahl().getPk());
      PraesentationStatistik praesentationStatistik = praesentationStatistikStore.getPraesentationStatistik(ergebnis.getPk());
      praesentationStatistik.startGesamt();
      try {
        long start = System.currentTimeMillis();

        LOG.info("Ergebnis wird exportiert: " + ergebnis.getWahl().getName() + "-" + ergebnis.getErgebnisart());
        PraesentationExportContext context = new PraesentationExportContext(wahldurchfuehrung, ergebnis, PRAESENTATIONSEXPORTAUSLOESER);
        DateiExportPK dateiExportPK = praesentationExporter
                .export(Praesentationsexportart.getFileFormat(), context, wahldurchfuehrung.getMandant(), wahldurchfuehrung, wahldurchfuehrung.getWahltermin());

        LOG.info(
                "Ergebnis ist exportiert: " + ergebnis.getWahl().getName() + "-" + ergebnis
                        .getErgebnisart() + ",dateiExportPK" + dateiExportPK + " - Dauer in ms: " + (System
                        .currentTimeMillis() - start));


        PraesentationUploadArt praesentationUploadArt = konfigurationLoader.resolveEnumValue(ElectKonfigurationWerte.PRAESENTATION_EXPORT_UPLOAD_ART, PraesentationUploadArt.class);
        if (praesentationUploadArt.isSynchron()) {
          LOG.info("Ergebnis wird synchron bereitgestellt - " + praesentationUploadArt);
          PraesentationBereitstellenStatus praesentationBereitstellenStatus = praesentationBereitstellenService
            .praesentationBereitstellen(dateiExportPK, ergebnis, praesentationStatistik);
          LOG.info("Ergebnis synchron bereitgestellt - Status " + praesentationBereitstellenStatus);
        } else if (praesentationUploadArt.isASynchron()) {
          LOG.info("Ergebnis wird asynchron bereitgestellt - " + praesentationUploadArt);
          praesentationBereitstellenService.praesentationAsynchronBereitstellen(dateiExportPK, ergebnis);
        } else {
          LOG.warn("Ergebnis nicht bereitgestellt - " + praesentationUploadArt);
        }

        postExport(wahldurchfuehrung);
      } finally {
        praesentationStatistik.endeGesamt();
      }
    } finally {
      zaehler.decrementAndGet();
    }
  }


  public static boolean laeuftGenerierung(WahlPK wahlPK) {
    if (!ANZAHL_LAUFENDE_GENERIERUNGEN_PRO_WAHL.containsKey(wahlPK)) {
      return false;
    }
    return ANZAHL_LAUFENDE_GENERIERUNGEN_PRO_WAHL.get(wahlPK).get() > 0;
  }

  protected void postExport(Wahldurchfuehrung wahldurchfuehrung) {
    // nichts zu
  }

  public boolean isErgebnisZuBerechnen(MandantPK mandantPK) {
    if (isDisabled()) {
      return false;
    }

    if (!isExportAktiviert()) {
      LOG.info("Export der Präsentation ist deaktiviert");
      return false;
    }

    return !getNaechstesZuExportierendesErgebnis(mandantDAO.findByPrimaryKey(mandantPK), PRAESENTATIONSEXPORTAUSLOESER, MAX_PRAESENTATION_PER_TRANSACTION)
        .isEmpty();
  }

  protected List<PresentationGenerierenZuExportierendesErgebnis> getNaechstesZuExportierendesErgebnis(Mandant mandant,
                                                                                                      Praesentationsexportausloeser praesentationsexportausloeser, long limit) {
    // Naive Implementierung, TODO optimieren?
    List<Wahldurchfuehrung> alleWahlen = new ArrayList<>(wahldurchfuehrungDAO.findByAll(mandant));
    Collections.shuffle(alleWahlen); // Schön zufällig
    return alleWahlen.stream().map(wahldurchfuehrung -> getNaechstesZuExportierendesErgebnisFuerWahl(wahldurchfuehrung, praesentationsexportausloeser))
        .filter(Objects::nonNull).limit(limit).collect(Collectors.toList());
  }

  @Nullable
  private PresentationGenerierenZuExportierendesErgebnis getNaechstesZuExportierendesErgebnisFuerWahl(Wahldurchfuehrung wahldurchfuehrung,
                                                                                                      Praesentationsexportausloeser praesentationsexportausloeser) {


    // ELECTKOM-84
    if (wahldurchfuehrungsgebietDAO.findByWahl(wahldurchfuehrung).isEmpty()) {
      // Systemmeldung nicht notwendig systemLogWriter.writeInfoLogMessageFuerMandant(wahldurchfuehrung, "Es wird keine Präsentation für " + wahldurchfuehrung.getName() + " generiert, da keine Gebiete vorhanden sind.", SystemLogQuelle.PRAESENTATION_GENERIEREN);
      return null;
    }

    PraesentationBereitstellen praesentationBereitstellen = wahlParameterLoader
        .resolveEnumValue(wahldurchfuehrung, WahlParameterWerte.PREASENTATIION_STATUS, PraesentationBereitstellen.class);
    LOG.info("Mandant: " + wahldurchfuehrung.getMandant().getNummer() + ", Wahl: " + wahldurchfuehrung.getId() + ", " + wahldurchfuehrung.getName() + ", wahlparameterPraesentationStatus: " + praesentationBereitstellen + ", Status: " + wahldurchfuehrung.getStatus());

    if (isPraesentationBereitstellen(wahldurchfuehrung, praesentationBereitstellen, wahldurchfuehrungsgebietDAO)) {

      Stimmabgabeart stimmabgabeart = Stimmabgabeart.GESAMT;
      Praesentationsexportart praesentationsexportart = Praesentationsexportart.INTERNET;
      for (Ergebnis ergebnis : ergebnisDAO
              .findByWahlAndErgebnisart(wahldurchfuehrung, wahldurchfuehrung.getErgebniseingangsart().korrespondierendeErgebnisart())) {

        // ELECTMUC-369 wenn es mehrere praesentationsexporte gibt, den mit dem kleinsten PK nehmen
        List<Praesentationsexport> liste = praesentationsexportDAO
                .findByErgebnisAndArtAndAusloeserAndStimmabgabeart(ergebnis, praesentationsexportart, praesentationsexportausloeser, stimmabgabeart);

        if (liste.isEmpty()) {
          LOG.info("Mandant: " + wahldurchfuehrung.getMandant().getNummer() + ", Wahl: " + wahldurchfuehrung.getId() + ", " + wahldurchfuehrung.getName() + " bis jetzt kein Export vorhanden");
          if (ergebnis.getErgebnisStatus().isBerechnet() && ergebnis.getEingangssequenznummer() != null) {
            return new PresentationGenerierenZuExportierendesErgebnis(ergebnis);
          } else {
            PraesentationAutomatischenExportAnstossen praesentationAutomatischenExportAnstossen = wahlParameterLoader
                    .resolveEnumValue(ergebnis.getWahl(), WahlParameterWerte.PREASENTATIION_AUTOMATISCHEN_EXPORT_ANSTOSSEN,
                            PraesentationAutomatischenExportAnstossen.class);
            if (praesentationAutomatischenExportAnstossen != null && praesentationAutomatischenExportAnstossen.isNachZuruecksetzenAnstossen()) {
              return new PresentationGenerierenZuExportierendesErgebnis(ergebnis);
            }
            break; // hier nichts weiter zu tun
          }
        }

        if (liste.size() > 1) {
          LOG.warn(
                  "Mehr als ein Praesentationsexport vorhanden: " + ergebnis + ", " + praesentationsexportart + ", " + praesentationsexportausloeser + ", " + stimmabgabeart);
        }

        Praesentationsexport praesentationsexportFinal = liste.get(0);
        for (Praesentationsexport praesentationsexport : liste) {
          if (praesentationsexportFinal.getPk().compareTo(praesentationsexport.getPk()) > 0) {
            praesentationsexportFinal = praesentationsexport;
          }
        }
        if (PraesentationExportObjectErzeuger.NUMMER_FUER_ATOMATISCHEN_EXPORT == praesentationsexportFinal.getLetzteEingangssequenznummer()) {
          return new PresentationGenerierenZuExportierendesErgebnis(ergebnis);
        }
        if (ergebnis.getErgebnisStatus().isBerechnet() && ergebnis.getEingangssequenznummer() != null) {
          if (praesentationsexportFinal.getLetzteEingangssequenznummer() < ergebnis.getEingangssequenznummer()) {
            LOG.info("Wahl: " + wahldurchfuehrung.getId() + ", " + wahldurchfuehrung.getName() + " letzter Export nach Stimmeneingang");
            return new PresentationGenerierenZuExportierendesErgebnis(ergebnis);
          }
        }

      }
      LOG.info("Mandant: " + wahldurchfuehrung.getMandant().getNummer() + ", Wahl: " + wahldurchfuehrung.getId() + ", " + wahldurchfuehrung
          .getName() + " kein Export notwendig");
    }
    return null;
  }

  public void preasentationFuerErgebnisZurueckSetzen(Ergebnis ergebnis) {
    preasentationFuerErgebnisSetzen(ergebnis, -1L);
  }
  public void preasentationFuerErgebnisStarten(Ergebnis ergebnis) {
    preasentationFuerErgebnisSetzen(ergebnis, PraesentationExportObjectErzeuger.NUMMER_FUER_ATOMATISCHEN_EXPORT);
  }

  private void preasentationFuerErgebnisSetzen(Ergebnis ergebnis, Long sequenceNummer) {
    for (Praesentationsexport praesentationsexport : praesentationsexportDAO.findByByErgebnis(ergebnis)) {
      if (Praesentationsexportausloeser.SYSTEM.equals(praesentationsexport.getPraesentationsexportausloeser())) {
        praesentationsexport.setLetzteEingangssequenznummer(sequenceNummer); // automatischen Export zurücksetzen
      }
    }
  }

  public static boolean isPraesentationBereitstellen(Wahldurchfuehrung wahldurchfuehrung, PraesentationBereitstellen praesentationBereitstellen, AbstractWahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO) {
    return praesentationBereitstellen != null /*kann beim ersten Start null sein - theoretisch*/ && praesentationBereitstellen
        .isAktiviert(wahldurchfuehrung.getErgebniseingangsart().korrespondierendeErgebnisart(),
            () -> wahldurchfuehrung.getWurzelgebiet(wahldurchfuehrungsgebietDAO).castTo(
                Wahldurchfuehrungsgebiet.class).getEntity().getErgebniseingabestatus(wahldurchfuehrung.getErgebniseingangsart().korrespondierendeErgebnisart())
                .isFreigegeben()) && wahldurchfuehrung.getStatus().isAuswaehlbar();
  }

  private boolean isExportAktiviert() {
    try {
      return konfigurationLoader
              .resolveBooleanValue(ElectKonfigurationWerte.PRAESENTATION_EXPORT_INTERNET_AKTIVIERT) && internetPraesentationErmittler
              .getInternetPraesentationTyp().isPraesentationVerfuegbar();
    } catch (Exception e) {
      LOG.warn("Keine Werte in Konfigurationstabelle? " + e.getMessage());
    }
    return false;
  }

  protected boolean isDisabled() {
    boolean disabled = !presentationGenerierenEnabled.isEnabled();
    PresentationGenerierenInternetBackgroundService.DISABLED.set(disabled);
    return disabled;
  }


}
