
package de.ivu.elect.presentation.ergebnis.dokumente.gesamtergebnis;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.annotation.Nullable;
import javax.enterprise.inject.Specializes;
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.EmlImportMapperHelper;
import de.ivu.elect.business.CurrentCustomer;
import de.ivu.elect.business.dokumente.control.DokumentExtraFileOutput;
import de.ivu.elect.business.ergebnis.boundary.GesamtErgebnisDokumentContext;
import de.ivu.elect.business.ergebnis.control.ErgebnisExportContext;
import de.ivu.elect.business.ergebnis.control.ErgebnisExportObject;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.wahl.entity.NlWahlart;
import de.ivu.elect.dokumente.WasElectContext;
import de.ivu.elect.jee.exception.ElectCommonException;
import de.ivu.elect.nl.eml.EmlWrapper;
import de.ivu.elect.nl.eml.EmlWriter;
import de.ivu.elect.presentation.ergebnis.exchange.NlWasErgebnisArtefaktNachGebietKorrekturBulkExportView;
import de.ivu.elect.was.exchange.diff.Eml510ErgebnisDifferenzBerechner;
import de.ivu.elect.was.exchange.diff.EmlErgebnisDifferenz;
import de.ivu.elect.was.exchange.ergebnis.eml.Eml510bPSBErgebnisExportMapper;
import de.ivu.elect.was.exchange.ergebnis.eml.Eml510cHSBErgebnisExportMapper;
import de.ivu.wahl.wus.oasis.eml.EML;

@Specializes // ELECT-10670
public class NlDefaultGesamtErgebnisDokumentKonverter extends DefaultGesamtErgebnisDokumentKonverter {

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

  public static final String EML_ERGEBNIS_DIFFERENZ_KEY = "EML_ERGEBNIS_DIFFERENZ";

  @Inject
  private transient NlGesamtErgebnisDokumentDiffFilter nlGesamtErgebnisDokumentDiffFilter;

  @Inject
  private transient Eml510bPSBErgebnisExportMapper eml510bPSBErgebnisExportMapper;

  @Inject
  private transient Eml510cHSBErgebnisExportMapper eml510cHSBErgebnisExportMapper;

  @Inject
  private transient CurrentCustomer currentCustomer;

  public NlDefaultGesamtErgebnisDokumentKonverter() {
    super();
  }

  @Override
  public GesamtErgebnisDokument konvertiere(Ergebnis ergebnis, GesamtErgebnisDokumentContext context, DokumentExtraFileOutput dokumentExtraFileOutput) {
    var emlErgebnisDifferenz = context.get(EML_ERGEBNIS_DIFFERENZ_KEY, EmlErgebnisDifferenz.class);

    if (emlErgebnisDifferenz == null) {
      var uploadedFile = context.get(NlWasErgebnisArtefaktNachGebietKorrekturBulkExportView.EML_ERGEBNIS_DIFFERENZ_UPLOADED_FILE, UploadedFile.class);
      if (uploadedFile != null) {
        try {
          var ergebnisDaten = context.getErgebnisDaten();
          var ergebnisExportObject = new ErgebnisExportObject(ergebnisDaten, null);
          var ergebnisExportContext = new ErgebnisExportContext(ergebnisDaten.getErgebnis());
          var emlNachher = getAktuellesEml(ergebnis, ergebnisExportObject, ergebnisExportContext);
          var emlVorher = liesEmlVorher(uploadedFile, emlNachher.getId()/*ELECT-15695*/);
          if (emlVorher == null) {
            throw new ElectCommonException("file.format.ergebnisdokument.export.eml.upload.missing");
          }

          emlErgebnisDifferenz = new Eml510ErgebnisDifferenzBerechner(emlVorher, emlNachher).berechneDifferenz();
          context.put(EML_ERGEBNIS_DIFFERENZ_KEY, emlErgebnisDifferenz);
        } catch (ElectCommonException e) {
          throw e;
        } catch (Exception e) {
          throw new RuntimeException(e.getMessage(), e);
        }
      }
    }

    var dokumentKey = createContextParameterKey(context);
    if (emlErgebnisDifferenz == null) {
      if (context.containsKey(dokumentKey)) {
        var dokument = context.get(dokumentKey, GesamtErgebnisDokument.class);

        if (dokument != null) {
          info("Verwende GECACHTES Dokument [" //
              + "dokumentKey = " + dokumentKey + ", " //
              + "dokument = " + dokument //
              + "]");

          return dokument;
        }
      }

    }
    var dokument = super.konvertiere(ergebnis, context, dokumentExtraFileOutput);

    if (emlErgebnisDifferenz == null) {
      context.put(dokumentKey, dokument);
    }

    info("Verwende NEUES Dokument [" //
        + "dokumentKey = " + dokumentKey + ", " //
        + "dokument = " + dokument //
        + "]");

    if (emlErgebnisDifferenz == null) {
      return dokument;
    }

    info("Filtere Dokument: " + emlErgebnisDifferenz);
    return nlGesamtErgebnisDokumentDiffFilter.filter(dokument, emlErgebnisDifferenz);
  }

  @Nullable
  private EML liesEmlVorher(UploadedFile uploadedFile, String benoetigteId) {
    try (var bos = new ByteArrayOutputStream()) {
      IOUtils.copy(uploadedFile.getInputStream(), bos);
      return liesEml(bos.toByteArray(), benoetigteId);
    } catch (IOException e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }

  @Nullable
  private EML liesEml(byte[] content, String benoetigteId) {
    try (var bis = new ByteArrayInputStream(content)) {
      LOG.info("Eml-direkt");
      var eml = new EmlImportMapperHelper().readEmlNoHash(bis).getContent();
      if (eml != null && /*ELECT-15695*/eml.getId().equalsIgnoreCase(benoetigteId)) {
        return eml;
      }
    } catch (Exception e) {
      LOG.info("Keine eml-Datei: " + e.getMessage());
    }

    try (var bis = new ByteArrayInputStream(content)) {
      try (var zis = new ZipInputStream(bis) {
        @Override
        public void close() throws IOException {
          // nicht schließen
        }
      }) {
        ZipEntry nextEntry;
        while ((nextEntry = zis.getNextEntry()) != null) {
          LOG.info("Entry: " + nextEntry.getName());
          try (var bos = new ByteArrayOutputStream()) {
            IOUtils.copy(zis, bos);
            var eml = liesEml(bos.toByteArray(), benoetigteId);
            if (eml != null) {
              return eml;
            }
          }
        }
      }
    } catch (Exception e) {
      LOG.info("Keine zip-Datei: " + e.getMessage());
    }

    return null;

  }

  private EML getAktuellesEml(Ergebnis ergebnis, ErgebnisExportObject ergebnisExportObject, ErgebnisExportContext ergebnisExportContext) throws IOException {
    EmlWrapper emlWrapper;
    if (NlWahlart.isWasPSB(ergebnis.getWahl(), currentCustomer.getCustomer())) {
      emlWrapper = eml510bPSBErgebnisExportMapper.createEml(ergebnisExportObject, ergebnisExportContext);
    } else if (NlWahlart.isWasHSB(ergebnis.getWahl(), currentCustomer.getCustomer())) {
      emlWrapper = eml510cHSBErgebnisExportMapper.createEml(ergebnisExportObject, ergebnisExportContext);
    } else {
      throw new IllegalStateException("kein hsb oder psb");
    }

    // Nochmal serialisieren und deserialisieren, damit auf gleiche Weise erzeugt wie emlVorher
    try (var bos = new ByteArrayOutputStream()) {
      new EmlWriter().writeEMlHash(emlWrapper, bos, null);
      try (InputStream bis = new ByteArrayInputStream(bos.toByteArray())) {
        return new EmlImportMapperHelper().readEmlNoHash(bis).getContent();
      }
    }
  }

  @Override
  protected WasElectContext createWasElectContext(GesamtErgebnisDokumentContext context, DokumentExtraFileOutput dokumentExtraFileOutput) {
    var wasElectContext = super.createWasElectContext(context, dokumentExtraFileOutput);
    // EMLDiff NlErgebnisDatenCustomDataDocument weiterreichen
    wasElectContext.put(EML_ERGEBNIS_DIFFERENZ_KEY, context.get(EML_ERGEBNIS_DIFFERENZ_KEY, EmlErgebnisDifferenz.class));
    return wasElectContext;
  }

  private String createContextParameterKey(GesamtErgebnisDokumentContext context) {
    return createContextParameterKey("NlDefaultGesamtErgebnisDokumentKonverter#GesamtErgebnisDokument@", context.getGebiete());
  }

  public static String createContextParameterKey(String parameterKeyPrefix, Collection<Wahldurchfuehrungsgebiet> gebiete) {
    var parameterKeyHash = System.nanoTime();

    if (gebiete != null && !gebiete.isEmpty()) {
      List<Wahldurchfuehrungsgebiet> auswertungsgebiete = new ArrayList<>(gebiete);
      auswertungsgebiete.sort(GebietByBreadthFirstComparator.INSTANCE);

      parameterKeyHash = Arrays.hashCode(auswertungsgebiete.toArray(new Wahldurchfuehrungsgebiet[auswertungsgebiete.size()]));
    }

    return parameterKeyPrefix + parameterKeyHash; // ELECT-13362, eineindeutigen Key per Auswertungsgebiete erzeugen!!!
  }

  // TODO REMOVE

  protected EML read(String name) {
    try (var is = new FileInputStream(name)) {
      return new EmlImportMapperHelper().readEmlNoHash(is).getContent();
    } catch (Exception e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }
}
