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

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Date;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.inject.Inject;

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

import de.ivu.elect.business.datei.boundary.DateiExportDAO;
import de.ivu.elect.business.datei.entity.Datei;
import de.ivu.elect.business.datei.entity.DateiExportPK;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.exchange.control.ImportExportStreamCreator;
import de.ivu.elect.business.internet.boundary.PraesentationUploadStatusStore;
import de.ivu.elect.business.internet.entity.PraesentationUploadStatusEntry;
import de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte;
import de.ivu.elect.business.konfiguration.entity.KonfigurationLoader;
import de.ivu.elect.business.praesentation.entity.PraesentationStatistik;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.system.entity.SystemLogQuelle;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlParameterLoader;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.jee.faces.Messages;

public class PraesentationBereitstellenService implements Serializable {

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

  public static final String SEPARATOR = "-";

  private static final long serialVersionUID = 1L;

  @Inject
  private transient PraesentationUploadServiceControl praesentationUploadServiceControl;

  @Inject
  protected transient DateiExportDAO dateiExportDAO;

  @Inject
  protected transient KonfigurationLoader konfigurationLoader;

  @Inject
  protected transient ImportExportStreamCreator streamCreator;

  @Inject
  protected transient SystemLogWriter systemLogWriter;

  @Inject
  private transient WahlParameterLoader wahlParameterLoader;

  @Inject
  private transient Messages messages;

  @Inject
  private transient PraesentationAsynchronBereitstellenEvent praesentationAsynchronBereitstellenEvent;

  @Inject
  private transient PraesentationUploadStatusStore praesentationUploadStatusStore;

  public PraesentationBereitstellenStatus praesentationBereitstellen(DateiExportPK dateiExportPK, Ergebnis ergebnis,
                                                                     PraesentationStatistik praesentationStatistik, boolean isAsynchroneBereitstellung) {
    LOG.info("Stelle Präsentation bereit: " + ergebnis + ", " + dateiExportPK + ", " + isAsynchroneBereitstellung);
    var wahldurchfuehrung = ergebnis.getWahl().castTo(Wahldurchfuehrung.class).getEntity();
    var start = System.currentTimeMillis();
    var error = new AtomicBoolean(false);
    var bytes = new AtomicLong(0L);
    var dateien = new AtomicLong(0L);
    var dateienSkipped = new AtomicLong(0L);

    try {
      var datei = dateiExportDAO.findByPrimaryKey(dateiExportPK).getDatei();
      legaycyAblage(ergebnis, wahldurchfuehrung, datei);

      // Upload?
      if (praesentationUploadServiceControl.isUploadAktiviert(wahldurchfuehrung)) {
        praesentationStatistik.startUpload();
        try (var inputStream = datei.createInputStream(streamCreator)) {
          var praesentationUploadUploadStatistik = praesentationUploadServiceControl.uploadFileIfPossible(wahldurchfuehrung,
              inputStream, datei);

          dateien.addAndGet(praesentationUploadUploadStatistik.getGroesseNachDateiNamen().size());
          bytes.addAndGet(praesentationUploadUploadStatistik.getGroesseNachDateiNamen().values().stream().mapToLong(g -> g).sum());

          dateienSkipped.addAndGet(praesentationUploadUploadStatistik.getSkipped().size());

        } finally {
          praesentationStatistik.endeUpload();
        }
      }

    } catch (Exception e) {
      error.set(true);
      systemLogWriter
          .writeErrorLogMessageFuerMandantUndSystemMandant(wahldurchfuehrung, messages.getMessageForMessageKey("praesentation.upload.error",
                  wahldurchfuehrung.getMandant().getName() + "(" + wahldurchfuehrung.getMandant().getNummer() + ")",
                  wahldurchfuehrung.getTk().toStringLokalisiert(messages), ergebnis.getTk().toStringLokalisiert(messages)), e,
              SystemLogQuelle.PRAESENTATION_BEREITSTELLEN);
      return PraesentationBereitstellenStatus.FEHLERHAFT;
    } finally {
      praesentationUploadStatusStore.put(ergebnis.getPk(),
          new PraesentationUploadStatusEntry(new Date(start), new Date(System.currentTimeMillis()), error.get(),
              isAsynchroneBereitstellung, dateien.get(), bytes.get(), dateienSkipped.get()));
    }
    LOG.info("Stelle Präsentation bereit: " + ergebnis + ", " + dateiExportPK + " - done");
    return PraesentationBereitstellenStatus.FEHLERFREI;
  }

  private void legaycyAblage(Ergebnis ergebnis, Wahldurchfuehrung wahldurchfuehrung, Datei datei) throws IOException {
    var ablageverzeichnisKonfiguriert = konfigurationLoader.resolveStringValue(ElectKonfigurationWerte.PRAESENTATION_EXPORT_ABLAGEVERZEICHNIS);
    if (StringUtils.isEmpty(ablageverzeichnisKonfiguriert)) {
      // nichts zu tun LOG.info("Ablageverzeichnis für Internetexport nicht definiert, erstellte Praesentationsdatei wird nicht kopiert");
    } else {
      var zielverzeichnis = bestimmeZielverzeichnis(/* praesentationsexportart, */ ablageverzeichnisKonfiguriert, ergebnis/* , stimmabgabeart */);

      var targetDir = new File(zielverzeichnis);
      if (!targetDir.isDirectory()) {
        try {
          targetDir.mkdirs();
        } catch (Exception e) {
          LOG.warn("Fehler beim Erstellen den Verzeichnisses: '" + zielverzeichnis + "', " + e.getMessage());
        }
      }

      if (targetDir.isDirectory()) {
        var inputStream = datei.createInputStream(streamCreator);
        var alsZipAblegen = konfigurationLoader
            .resolveBooleanValue(ElectKonfigurationWerte.PRAESENTATION_EXPORT_ALS_ZIP_DATEI);
        if (alsZipAblegen) {
          var zieldatei = new File(zielverzeichnis + File.separator + datei.getOriginalDateiname());
          LOG.info("Praesentationsdatei wird kopiert: '" + zieldatei.getAbsolutePath() + "'");
          FileUtils.copyInputStreamToFile(inputStream, zieldatei);
          LOG.info("Praesentationsdatei ist kopiert: '" + zieldatei.getAbsolutePath() + "'");
        } else {
          LOG.info("Praesentationsdatei wird entpackt: '" + zielverzeichnis + "'");
          try (var zis = new ZipInputStream(inputStream) {

            @Override
            public void close() throws IOException {
              // nichts tun... copyInputStreamToFile schließt sonst den ZIP-InputStream...
            }
          }) {
            ZipEntry zipentry;
            while ((zipentry = zis.getNextEntry()) != null) {
              var destination = new File(zielverzeichnis + File.separator + zipentry.getName());
              if (zipentry.isDirectory()) {
                destination.mkdirs();
              } else {
                FileUtils.copyInputStreamToFile(zis, destination);
              }
              zis.closeEntry();
            }
          } finally {
            inputStream.close();
          }
          LOG.info("Praesentationsdatei ist entpackt: '" + zielverzeichnis + "'");
        }
      } else {
        systemLogWriter
            .writeWarnLogMessageFuerMandant(wahldurchfuehrung,
                "Praesentationsdatei kann nicht kopiert werden, Verzeichnis nicht vorhanden: '" + zielverzeichnis + "', Wahl: " + wahldurchfuehrung.getTk()
                    .toStringLokalisiert(messages), SystemLogQuelle.PRAESENTATION_BEREITSTELLEN);
      }
    }
  }

  private boolean isAsynchroneBereitstellungNotwendig(Wahl wahldurchfuehrung) {
    return StringUtils.isNotBlank(konfigurationLoader.resolveStringValue(ElectKonfigurationWerte.PRAESENTATION_EXPORT_ABLAGEVERZEICHNIS))
        || praesentationUploadServiceControl.isUploadAktiviert(wahldurchfuehrung);
  }

  public static String bestimmeZielverzeichnis(/* Praesentationsexportart praesentationsexportart, */
      String ablageverzeichnisKonfiguriert, Ergebnis ergebnis/* , Stimmabgabeart stimmabgabeart */) {
    return ablageverzeichnisKonfiguriert + File.separator + erzeugeVerzeichnisname(ergebnis);
    /*
     * ELECT-3588 - Internetpräsentation: Exportverzeichnis straffen + File.separator + praesentationsexportart.getNameFuerExportunterverzeichnis().toLowerCase() + File.separator +
     * stimmabgabeart.name().toLowerCase();
     */
  }

  private static String erzeugeVerzeichnisname(Ergebnis ergebnis) {
    return ergebnis.getErgebnisartPathMitWahlnameFilesystemKompatibel();
  }

  public void praesentationAsynchronBereitstellen(DateiExportPK dateiExportPK, Ergebnis ergebnis) {
    LOG.info("Präsentation für asynchrone Bereitstellung markieren: " + ergebnis + ", " + dateiExportPK);
    if (isAsynchroneBereitstellungNotwendig(ergebnis.getWahl())) {
      var parameter = wahlParameterLoader.getParameter(ergebnis.getWahl(), WahlParameterWerte.PREASENTATIION_ASYNCHRONE_BEREITSTELLUNG_NOTWENDIG);
      parameter.setWert(dateiExportPK.getId() + SEPARATOR + ergebnis.getId());
      praesentationAsynchronBereitstellenEvent.sendEvent();
    } else {
      LOG.info("Keine Bereitstellung konfigurert: " + ergebnis + ", " + dateiExportPK);
    }
  }
}

