
package de.ivu.elect.business.stimmeingabe.boundary;

import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
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 java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;

import org.apache.commons.io.IOUtils;
import org.primefaces.model.file.UploadedFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.datei.boundary.DateiExportEdit;
import de.ivu.elect.business.datei.control.FileExchange;
import de.ivu.elect.business.datei.entity.DateiExport;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.datei.entity.ServerFileType;
import de.ivu.elect.business.dokumente.boundary.AbstractDokumentGenerator;
import de.ivu.elect.business.dokumente.control.AbstractDokumentMerger;
import de.ivu.elect.business.dokumente.control.ProgressMonitor;
import de.ivu.elect.business.dokumente.entity.DokumentVorlage;
import de.ivu.elect.business.dokumente.entity.DokumentVorlageArt;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.control.ImportExportStreamCreator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeDocumentByErgebniseingangExportContext;
import de.ivu.elect.business.stimmeingabe.control.WasStimmeingabeDocumentByErgebniseingangExportObject;
import de.ivu.elect.business.stimmeingabe.entity.AbstractErgebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingangGruppierung;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.jee.performance.PerformanceLog;
import de.ivu.elect.jee.performance.PerformanceLogTransactionHandling;
import de.ivu.elect.jee.performance.ViewType;
import de.ivu.elect.presentation.dokumente.DokumentErzeugenResult;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;
import de.ivu.elect.util.FileUtil;

@Stateless
public class StimmeingabeDocumentByErgebniseingangDokumentGenerator
    extends
    AbstractDokumentGenerator<WasStimmeingabeDocumentByErgebniseingangExportObject, StimmeingabeDocumentByErgebniseingangExportContext> {

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

  @Inject
  private transient ErgebniseingangDAO ergebniseingangDAO;

  @Inject
  private transient FileExchange fileExchange;

  @Inject
  private transient ImportExportStreamCreator importExportStreamCreator;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient DateiExportEdit dateiExportEdit;

  public StimmeingabeDocumentByErgebniseingangDokumentGenerator() {
    super();
  }

  @Override
  public DokumentVorlageArt getDokumentVorlageArt() {
    return DokumentVorlageArt.STIMMEINGABE;
  }

  public DokumentErzeugenResult generiereDokumentSameTransaction(List<WasStimmeingabeDocumentByErgebniseingangExportObject> entitiesNotAttached,
                                             DokumentVorlage dokumentVorlageNotAttached, UploadedFile uploadedFile, FileType zieldateiTyp, Wahl wahlNotAttached, Wahltermin wahlterminNotAttached, StimmeingabeDocumentByErgebniseingangExportContext context, ProgressMonitor monitor) {
    return super.generiereDokument(entitiesNotAttached, dokumentVorlageNotAttached, uploadedFile, zieldateiTyp, wahlNotAttached, wahlterminNotAttached, context, monitor);
  }

  @Override
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  @PerformanceLog(viewType = ViewType.EXPORT, transactionHandling = PerformanceLogTransactionHandling.IMMEDIATELY)
  public DokumentErzeugenResult generiereDokument(List<WasStimmeingabeDocumentByErgebniseingangExportObject> entitiesNotAttached,
                                             DokumentVorlage dokumentVorlageNotAttached,
      UploadedFile uploadedFile, FileType zieldateiTyp, Wahl wahlNotAttached, Wahltermin wahlterminNotAttached, StimmeingabeDocumentByErgebniseingangExportContext context, ProgressMonitor monitor) {
    LOG.info("Erzeuge Dokument zu Erfassungsgebiet [dokument wahlgebiet required = " + context
        .isDocumentFuerWahlgebietRequired() + ", section stimmzettel included = "
        + !context.isSectionErfassteStimmzettelExcluded() + ", section zaehlliste included = " + !context
        .isSectionZaehllisteElektronischExcluded() + ", eingaben included = "
        + !context.isEingabenExcluded() + "]");

    ErgebniseingangGruppierung ergebniseingangGruppierung = context.get(ErgebniseingangGruppierung.class.getName(), ErgebniseingangGruppierung.class);
    if (ergebniseingangGruppierung == null) {
      ergebniseingangGruppierung = ErgebniseingangGruppierung.OHNE;
    }

    if (!entitiesNotAttached.isEmpty() && ergebniseingangGruppierung.isGruppiereNachGebiet()) {
      return gruppiertNachGebiet(entitiesNotAttached, dokumentVorlageNotAttached, uploadedFile, zieldateiTyp, wahlNotAttached, wahlterminNotAttached, context,
          monitor);
    }

    return super.generiereDokument(entitiesNotAttached, dokumentVorlageNotAttached, uploadedFile, zieldateiTyp, wahlNotAttached, wahlterminNotAttached, context,
        monitor);
  }

  private DokumentErzeugenResult gruppiertNachGebiet(List<WasStimmeingabeDocumentByErgebniseingangExportObject> entitiesNotAttached, DokumentVorlage dokumentVorlageNotAttached, UploadedFile uploadedFile, FileType zieldateiTyp, Wahl wahlNotAttached, Wahltermin wahlterminNotAttached, StimmeingabeDocumentByErgebniseingangExportContext context, ProgressMonitor monitor) {
    // ELECT-12655
    try {
      Map<Gebiet, List<WasStimmeingabeDocumentByErgebniseingangExportObject>> gruppiert = gruppiereNachGebiet(entitiesNotAttached, context);
      DokumentVorlage dokumentVorlage = dokumentVorlageDAO.attach(dokumentVorlageNotAttached);
      Wahl wahl = wahlAttacher.attach(wahlNotAttached);
      DateiExport dateiExport = fileExchange
          .createAndPersistDateiExport(AbstractDokumentMerger.DATEI_EXPORT_ART, new FileFormat(AbstractDokumentMerger.FILE_FORMAT_NAME, FileType.ZIP),
              getDokumentFileNames(dokumentVorlage, uploadedFile, context), wahl.getMandant(), wahl, wahl.getWahltermin(), ServerFileType.GENERIERTES_DOKUMENT);
      try (OutputStream os = dateiExport.getDatei().createOutputStream(importExportStreamCreator)) {
        try (ZipOutputStream zos = new ZipOutputStream(os)) {
          for (Map.Entry<Gebiet, List<WasStimmeingabeDocumentByErgebniseingangExportObject>> entry : gruppiert.entrySet()) {
            Gebiet key = entry.getKey();
            List<WasStimmeingabeDocumentByErgebniseingangExportObject> eingaengeImGebiet = entry.getValue();

            Date zeitstempelLetzterEingang = bestimmeZeitstempelLetzterEingang(eingaengeImGebiet);

            DokumentErzeugenResult dokumentErzeugenResult = super
                .generiereDokument(eingaengeImGebiet, dokumentVorlageNotAttached, uploadedFile, zieldateiTyp, wahlNotAttached, wahlterminNotAttached, context,
                    monitor);
            if (dokumentErzeugenResult.hasException() || !dokumentErzeugenResult.hasDokument()) {
              return dokumentErzeugenResult;
            }
            zos.putNextEntry(new ZipEntry(FileUtil
                .normalizeZipEntryName(gebietNummerFormatter.getDarstellung(key)) + "_" + new SimpleDateFormat("yyyyMMdd_HHmmss")
                .format(zeitstempelLetzterEingang) +
                "." + zieldateiTyp.getFileExtension()));

            DateiExport genertatedFile = dateiExportDAO.findByPrimaryKey(dokumentErzeugenResult.getDokument());
            try (InputStream is = genertatedFile.getDatei().createInputStream(importExportStreamCreator)) {
              IOUtils.copy(is, zos);
            }
            zos.closeEntry();
            dateiExportEdit.delete(genertatedFile);
          }
          zos.finish();
        }
      }
      return DokumentErzeugenResult.success(dateiExport.getPk());
    } catch (Exception e) {
      return DokumentErzeugenResult.error(e);
    }
  }

  private Date bestimmeZeitstempelLetzterEingang(List<WasStimmeingabeDocumentByErgebniseingangExportObject> eingaengeImGebiet) {
    Date result = null;
    for (WasStimmeingabeDocumentByErgebniseingangExportObject exportObject : eingaengeImGebiet) {
      for (Ergebniseingang ergebniseingang : exportObject.getErgebniseingaenge()) {
        if (result == null || result.before(ergebniseingang.getZeitstempel())) {
          result = ergebniseingang.getZeitstempel();
        }
      }
    }

    if (result == null) {
      result = new Date();
    }

    return result;
  }

  private Map<Gebiet, List<WasStimmeingabeDocumentByErgebniseingangExportObject>> gruppiereNachGebiet(List<WasStimmeingabeDocumentByErgebniseingangExportObject> entitiesNotAttached, StimmeingabeDocumentByErgebniseingangExportContext context) {
    Set<Ergebniseingang> ergebniseingaenge = new HashSet<>();
    for (WasStimmeingabeDocumentByErgebniseingangExportObject wasStimmeingabeDocumentByErgebniseingangExportObject : entitiesNotAttached) {
      ergebniseingaenge.addAll(attach(wasStimmeingabeDocumentByErgebniseingangExportObject, context).getErgebniseingaenge());
    }

    Map<Gebiet, List<Ergebniseingang>> collect = ergebniseingaenge.stream().collect(Collectors.groupingBy(AbstractErgebniseingang::getGebiet));

    Map<Gebiet, List<WasStimmeingabeDocumentByErgebniseingangExportObject>> result = new HashMap<>();

    for (Map.Entry<Gebiet, List<Ergebniseingang>> entry : collect.entrySet()) {
      result.put(entry.getKey(), Collections.singletonList(new WasStimmeingabeDocumentByErgebniseingangExportObject(entry.getValue())));
    }

    return result;

  }

  @Override
  protected WasStimmeingabeDocumentByErgebniseingangExportObject attach(WasStimmeingabeDocumentByErgebniseingangExportObject exportObject,
                                                                        StimmeingabeDocumentByErgebniseingangExportContext exportContext) {
    return new WasStimmeingabeDocumentByErgebniseingangExportObject(ergebniseingangDAO.attachAll(exportObject.getErgebniseingaenge()));
  }
}
