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

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

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

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

import de.ivu.elect.background.scheduler.AbstractSchedulerServiceTransaction;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisDAO;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.ErgebnisPK;
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.konfiguration.entity.ElectKonfigurationWerte;
import de.ivu.elect.business.konfiguration.entity.KonfigurationLoader;
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.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 extends AbstractSchedulerServiceTransaction<ErgebnisPK> {

  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<>();

  @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 PraesentationBereitstellenService praesentationBereitstellenService;

  public PresentationGenerieren() {
    super();
  }

  @Override
  public Set<ErgebnisPK> bestimmeNaechstePakete(Set<ErgebnisPK> inBearbeitung, int anzahlNeuerPakate) {
    if (isDisabled() || !isExportAktiviert()) {
      return Collections.emptySet();
    }

    Set<ErgebnisPK> result = new HashSet<>();

    for (var wahl : getAktivierteWahlenZufaellig()) {
      var wahldurchfuehrung = wahl.castTo(Wahldurchfuehrung.class).getEntity();
      var naechstesZuExportierendesErgebnisFuerWahl = getNaechstesZuExportierendesErgebnisFuerWahl(wahldurchfuehrung);
      if (naechstesZuExportierendesErgebnisFuerWahl != null && !inBearbeitung.contains(naechstesZuExportierendesErgebnisFuerWahl.getErgebnis().getPk())) {
        result.add(naechstesZuExportierendesErgebnisFuerWahl.getErgebnis().getPk());
        if (result.size() >= anzahlNeuerPakate) {
          return result;
        }
      }
    }

    return result;
  }

  @Override
  public void bearbeitePaket(ErgebnisPK paket) {
    erzeugePraesentationFuerErgebnis(new PresentationGenerierenZuExportierendesErgebnis(ergebnisDAO.findByPrimaryKey(paket)));
  }

  public void erzeugePraesentationFuerErgebnis(PresentationGenerierenZuExportierendesErgebnis zuExportieren) {
    if (isDisabled() || !isExportAktiviert()) {
      return;
    }
    var ergebnis = zuExportieren.getErgebnis();

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

      var wahldurchfuehrung = wahldurchfuehrungDAO.findByPrimaryKey(ergebnis.getWahl().getPk());
      var praesentationStatistik = praesentationStatistikStore.getPraesentationStatistik(ergebnis.getPk());
      praesentationStatistik.startGesamt();
      var error = new AtomicBoolean(false);
      try {
        var start = System.currentTimeMillis();

        LOG.info("Ergebnis wird exportiert: " + ergebnis.getWahl().getName() + "-" + ergebnis.getErgebnisart());
        var context = createPraesentationExportContext(ergebnis, wahldurchfuehrung);
        var 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));

        var praesentationUploadArt = konfigurationLoader.resolveEnumValue(ElectKonfigurationWerte.PRAESENTATION_EXPORT_UPLOAD_ART,
            PraesentationUploadArt.class);
        if (praesentationUploadArt.isSynchron()) {
          LOG.info("Ergebnis wird synchron bereitgestellt - " + praesentationUploadArt);
          var praesentationBereitstellenStatus = praesentationBereitstellenService
              .praesentationBereitstellen(dateiExportPK, ergebnis, praesentationStatistik, false);
          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);
        }

      } catch (Exception e) {
        error.set(true);
        throw e;
      } finally {
        praesentationStatistik.endeGesamt(error.get());
      }
    } finally {
      zaehler.decrementAndGet();
    }
  }

  private static PraesentationExportContext createPraesentationExportContext(Ergebnis ergebnis, Wahldurchfuehrung wahldurchfuehrung) {
    return new PraesentationExportContext(wahldurchfuehrung, ergebnis, PRAESENTATIONSEXPORTAUSLOESER);
  }

  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;
  }

  public boolean isErstellenMoeglich(PresentationGenerierenZuExportierendesErgebnis presentationGenerierenZuExportierendesErgebnis) {
    var ergebnis = ergebnisDAO.attach(presentationGenerierenZuExportierendesErgebnis.getErgebnis());
    // ELECT-14818
    if (ergebnis == null || ergebnis.getWahl() == null) {
      return false;
    }
    var context = createPraesentationExportContext(ergebnis, ergebnis.getWahl().castTo(Wahldurchfuehrung.class).getEntity());

    return praesentationExporter.isMapperVorhanden(context, Praesentationsexportart.getFileFormat());
  }

  @Nullable
  private PresentationGenerierenZuExportierendesErgebnis getNaechstesZuExportierendesErgebnisFuerWahl(Wahldurchfuehrung wahldurchfuehrung) {

    // 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;
    }

    var 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)) {

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

        if (isErstellenMoeglich(new PresentationGenerierenZuExportierendesErgebnis(ergebnis)) /*ELECT-15739*/) {
          // ELECTMUC-369 wenn es mehrere praesentationsexporte gibt, den mit dem kleinsten PK nehmen
          var 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 {
              var 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);
          }

          var praesentationsexportFinal = liste.get(0);
          for (var 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 (var 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(),
            () -> isWurzelgebietFreigegeben(wahldurchfuehrung, wahldurchfuehrungsgebietDAO)) && wahldurchfuehrung.getStatus().isAuswaehlbar();
  }

  private static boolean isWurzelgebietFreigegeben(Wahldurchfuehrung wahldurchfuehrung, AbstractWahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO) {
    var wurzelgebiet = wahldurchfuehrung.getWurzelgebiet(wahldurchfuehrungsgebietDAO);
    if (wurzelgebiet == null) {
      return false;
    }
    return wurzelgebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity()
        .getErgebniseingabestatus(wahldurchfuehrung.getErgebniseingangsart().korrespondierendeErgebnisart()).isFreigegeben();
  }

  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() {
    var disabled = !presentationGenerierenEnabled.isEnabled();
    PresentationGenerierenInternetBackgroundService.DISABLED.set(disabled);
    return disabled;
  }

}
