
package de.ivu.elect.was.exchange.stimmeingabe;

import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.PublicKey;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

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

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.EmlImportMapper;
import de.ivu.elect.EmlImportMapperHelper;
import de.ivu.elect.EmlValidation;
import de.ivu.elect.business.CurrentCustomer;
import de.ivu.elect.business.datei.entity.Datei;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.control.FileKonfiguration;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.gebietsbaum.boundary.GebietsarteigenschaftDAO;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietFactory;
import de.ivu.elect.business.gebietsbaum.entity.AbstractWahldurchfuehrungsgebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsarteigenschaft;
import de.ivu.elect.business.gebietsbaum.entity.NlGebietCustomData;
import de.ivu.elect.business.gebietsbaum.entity.NlGebietsart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.security.boundary.EmlSignKeyService;
import de.ivu.elect.business.stimmeingabe.boundary.NlVoteType;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeImportContext;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestufe;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingangErfasser;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsherkunft;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.wahl.entity.NlWahlart;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagDAO;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.jee.exchange.Violation;
import de.ivu.elect.jee.exchange.ViolationLevel;
import de.ivu.elect.jee.exchange.Violations;
import de.ivu.elect.jee.faces.DevelopmentStore;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.LambdaBooleanProperty;
import de.ivu.elect.nl.eml.EmlValidator;
import de.ivu.elect.nl.eml.EmlWrapper;
import de.ivu.elect.nl.eml.readable.AbstractVotesSectionWrapper;
import de.ivu.elect.nl.eml.readable.Eml510ContestWrapper;
import de.ivu.elect.nl.eml.readable.Eml510Wrapper;
import de.ivu.elect.nl.eml.readable.GroupVotesSection;
import de.ivu.elect.nl.eml.readable.ReportingUnitVotesWrapper;
import de.ivu.elect.nl.eml.readable.TotalVotesWrapper;
import de.ivu.elect.nl.eml.readable.VoteType;
import de.ivu.elect.nl.x509.NlCMS;
import de.ivu.elect.nl.x509.NlX509Metadata;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;
import de.ivu.wahl.wus.oasis.eml.EML;

public class Eml510StimmeingabeImportMapper extends AbstractStimmeingabeImportMapper implements EmlImportMapper {

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

  public static final String FILE_FORMAT = "eml.510.file.format";

  private static final long serialVersionUID = 1L;

  public static final String SB_PREFIX = "sb";

  public static final String EML_SIGNATUR_AS_BYTES = "EML_SIGNATUR";

  public static final String EML_SIGNATUR_OHNE = "EML_SIGNATUR_OHNE";

  private static final Pattern COMPILE = Pattern.compile("\\D+");

  public static final String EINGANGSTUFE_FORCIEREN = "eingangstufe.forcieren";

  @Inject
  private transient Eml510StimmeingabeImportMapperHelper eml510StimmeingabeImportMapperHelper;

  @Inject
  private transient EmlImportMapperHelper emlImportMapperHelper;

  @Inject
  private transient WahldurchfuehrungsgebietFactory wahldurchfuehrungsgebietFactory;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO; // NICHT ALS VORLAGE BENUTZEN (DAO IN MAPPER)

  @Inject
  private transient GebietsarteigenschaftDAO gebietsarteigenschaftDAO; // NICHT ALS VORLAGE BENUTZEN (DAO IN MAPPER)

  @Inject
  private transient CurrentCustomer currentCustomer;

  @Inject
  private transient WahlvorschlagDAO wahlvorschlagDAO;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  public Eml510StimmeingabeImportMapper() {
    super();
  }

  @Override
  public boolean isAcceptContext(StimmeingabeImportContext context) {
    return super.isAcceptContext(context) && !isBackupRestoreEnabled(context.getWahldurchfuehrung());
  }

  @Override
  protected boolean isAcceptWahl(Wahldurchfuehrung wahldurchfuehrung) {
    return isWahlvorschlaegeVorhanden(wahldurchfuehrung) && isErfassungsgebieteVorhanden(wahldurchfuehrung);
  }

  @Override
  public FileFormat getFileFormat() {
    return new FileFormat(FILE_FORMAT, FileType.XML);
  }

  @Override
  public List<Ergebniseingang> deserialize(InputStream is, StimmeingabeImportContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    List<Ergebniseingang> result = new ArrayList<>();

    EmlWrapper emlWrapper = emlImportMapperHelper.readEmlNoHash(is);

    Eml510Wrapper eml510Wrapper = new Eml510Wrapper(emlWrapper.getContent());

    Wahldurchfuehrung wahl = findWahl(context);

    Eml510 eml510 = Eml510.from(eml510Wrapper.getContent().getId());
    info("eml510: " + eml510);

    Ergebniseingabestufe ergebniseingabestufe = eml510StimmeingabeImportMapperHelper.bestimmeErgebniseingabestufe(eml510, wahl);

    if (context.get(EINGANGSTUFE_FORCIEREN, Ergebniseingabestufe.class) != null) {
      ergebniseingabestufe = context.get(EINGANGSTUFE_FORCIEREN, Ergebniseingabestufe.class);
      info("Ergebniseingabestufe forciert: " + ergebniseingabestufe);
    }

    Map<String, Gebietsart> erfassungsGebietsartenMitGebietsNummer = bestimmeErfassungsgebietsartenMitGebietsnummer(context);

    Gebietsart gebietsartErfassungsgebiete = bestimmeGebietartErfassungsgebiete(wahl);

    if (!eml510Wrapper.getContest().isEmpty()) {
      Eml510ContestWrapper eml510ContestWrapper = eml510Wrapper.getContest().get(0);
      EmlVoteSource emlVoteSource = bestimmeEmlVoteSource(eml510, wahl);
      info("emlVoteSource: " + emlVoteSource);

      if (emlVoteSource.isReportingUnit()) {
        for (ReportingUnitVotesWrapper reportingUnitVote : eml510ContestWrapper.getReportingUnitVotes()) {

          mappeErgbnisse(result, reportingUnitVote.getVotesWrapper(), reportingUnitVote.getName(), reportingUnitVote.getId(), ergebniseingabestufe, context, erfassungsGebietsartenMitGebietsNummer,
              gebietsartErfassungsgebiete);
        }
      }
      if (emlVoteSource.isTotalVotes()) {
        for (TotalVotesWrapper totalVote : eml510ContestWrapper.getTotalVotes()) {
          String gebietName = bestimmeNameGebietTotalVote(eml510Wrapper.getContent());
          String gebietNummerFormatiert = bestimmeNummerGebietTotalVote(eml510Wrapper.getContent());
          mappeErgbnisse(result, totalVote.getVotesWrapper(), gebietName, gebietNummerFormatiert, ergebniseingabestufe, context, erfassungsGebietsartenMitGebietsNummer, gebietsartErfassungsgebiete);
        }
      }

    }

    return result;
  }

  @Nullable
  private Gebietsart bestimmeGebietartErfassungsgebiete(Wahldurchfuehrung wahl) {
    Set<Gebietsart> gebietsartenErfassungsgebiete = gebietsarteigenschaftDAO.findByWahlart(wahl.getWahlart()).stream().filter(Gebietsarteigenschaft::getErfassungsgebiet)
        .map(Gebietsarteigenschaft::getGebietsart).collect(Collectors.toSet());

    Gebietsart gebietsartErfassungsgebiete = null;
    if (gebietsartenErfassungsgebiete.size() == 1) {
      gebietsartErfassungsgebiete = new Gebietsart();
      gebietsartErfassungsgebiete.setName(gebietsartenErfassungsgebiete.iterator().next().getName());
    }
    return gebietsartErfassungsgebiete;
  }

  private Map<String, Gebietsart> bestimmeErfassungsgebietsartenMitGebietsnummer(StimmeingabeImportContext context) {
    Map<String, Gebietsart> erfassungsGebietsartenMitGebietsNummer = new HashMap<>();
    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : wahldurchfuehrungsgebietDAO.findByWahl(findWahl(context))) {
      if (wahldurchfuehrungsgebiet.isErfassungsgebiet()) {
        Gebietsart gebietsart = new Gebietsart();
        gebietsart.setName(wahldurchfuehrungsgebiet.getGebietsart().getName());
        erfassungsGebietsartenMitGebietsNummer.put(wahldurchfuehrungsgebiet.getNummer(), gebietsart);
      }
    }
    return erfassungsGebietsartenMitGebietsNummer;
  }

  private EmlVoteSource bestimmeEmlVoteSource(Eml510 eml510, Wahldurchfuehrung wahl) {

    if (NlWahlart.isWasPSB(wahl, currentCustomer.getCustomer())) {
      info("isPSB");
      return eml510.getPsbImportSource();
    }
    if (NlWahlart.isWasHSB(wahl, currentCustomer.getCustomer())) {
      info("isHSB");
      return eml510.getHsbImportSource();
    }
    if (NlWahlart.isWasCSB(wahl, currentCustomer.getCustomer())) {
      info("isCSB");
      return eml510.getCsbImportSource();
    }

    return EmlVoteSource.UNSUPPORTED;

  }

  private String bestimmeNummerGebietTotalVote(EML content) {
    if (content != null && content.getManagingAuthority() != null && content.getManagingAuthority().getAuthorityIdentifier() != null) {
      return content.getManagingAuthority().getAuthorityIdentifier().getId();
    }
    return "";
  }

  private String bestimmeNameGebietTotalVote(EML content) {
    if (content != null && content.getManagingAuthority() != null && content.getManagingAuthority().getAuthorityIdentifier() != null) {
      return content.getManagingAuthority().getAuthorityIdentifier().getValue();
    }
    return "";
  }

  private void mappeErgbnisse(List<Ergebniseingang> result, AbstractVotesSectionWrapper< ? > votesWrapper, String gebietName, String gebietNummerFormatiert, Ergebniseingabestufe ergebniseingabestufe,
      StimmeingabeImportContext context, Map<String, Gebietsart> erfassungsGebietsartenMitGebietsNummer, Gebietsart gebietsartErfassungsgebiete) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = mappeWahldurchfuehrungsgebiet(gebietName, gebietNummerFormatiert, context, erfassungsGebietsartenMitGebietsNummer, gebietsartErfassungsgebiete);

    ErgebniseingangErfasser benutzer = context.getImportErfasser(); // ELECTVAPP-1002
    ErgebniseingangErfasser erfasser = ErgebniseingangErfasser.extern(getMessage("stimmeingabe.import.exchange.quelle", context.getOriginalDateiname(), benutzer.getKennung()));

    Ergebniseingang ergebniseingang = new Ergebniseingang();
    ergebniseingang.setEingangssequenznummer(1L); // erstmal auf irgendetwas, wird beim Speichern auf die korrekte nummer gesetzt
    ergebniseingang.setErgebniseingangsart(Ergebniseingangsart.ENDGUELTIG); // in NL immer Endgültig??
    ergebniseingang.setWahldurchfuehrungsgebiet(wahldurchfuehrungsgebiet);
    ergebniseingang.setErgebniseingangsherkunft(Ergebniseingangsherkunft.IMPORT);
    ergebniseingang.setErgebniseingangsfehlerstatus(null); // Eingang soll validiert werden
    ergebniseingang.setErfasser(erfasser.getKennung());
    ergebniseingang.setZeitstempel(new Date()); // Information nicht vorhanden...
    ergebniseingang.setErgebniseingabestufe(ergebniseingabestufe); // ELECT-10065

    addStimmeingabenSystemgruppen(votesWrapper.getValidVotes(), ergebniseingang);
    addStimmeingabenPolitischeGruppen(votesWrapper.getCandidateVotesPerGroup(), ergebniseingang);

    result.add(ergebniseingang);
  }

  private Wahldurchfuehrungsgebiet mappeWahldurchfuehrungsgebiet(String gebietName, String gebietNummerFormatiert, StimmeingabeImportContext context,
      Map<String, Gebietsart> erfassungsGebietsartenMitGebietsNummer, Gebietsart gebietsartErfassungsgebiete) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = wahldurchfuehrungsgebietFactory.create();
    wahldurchfuehrungsgebiet.setName(gebietName);
    wahldurchfuehrungsgebiet.setWahl(findWahl(context)); // für ELECT-10371 sonst NPE

    String gebietsNummer = bestimmeNummer(gebietNummerFormatiert);
    wahldurchfuehrungsgebiet.setNummer(gebietsNummer);
    Gebietsart gebietsart = bestimmeGebietsart(gebietNummerFormatiert, gebietsNummer, erfassungsGebietsartenMitGebietsNummer, gebietsartErfassungsgebiete);
    wahldurchfuehrungsgebiet.setGebietsart(gebietsart);

    if (wahldurchfuehrungsgebiet.getGebietsart() == null) {
      warn("Gebietart nicht gefunden: " + gebietNummerFormatiert + ", " + gebietsNummer + ", " + erfassungsGebietsartenMitGebietsNummer);
    }

    return wahldurchfuehrungsgebiet;
  }

  @Nullable
  private Gebietsart bestimmeGebietsart(String id, String nummer, Map<String, Gebietsart> erfassungsGebietsartenMitGebietsNummer, Gebietsart fallback) {
    // mehrere Fallbacks zum Bestimmen der Gebietsart

    if (id != null && id.startsWith(SB_PREFIX)) {
      Gebietsart gebietsart = new Gebietsart();
      gebietsart.setName(NlGebietsart.STIMMBEZIRK.name());
      return gebietsart;
    }

    Gebietsart gebietsart = erfassungsGebietsartenMitGebietsNummer.get(nummer);
    if (gebietsart != null) {
      return gebietsart;
    }

    return fallback;
  }

  static String bestimmeNummer(String id) {
    if (isBlank(id)) {
      return id;
    }

    try {
      return Long.toString(Long.parseLong(id)); // führende Nullen entfernen
    } catch (Exception e) {
      // ignore
    }
    if (id.toLowerCase().startsWith(SB_PREFIX)) {
      String result = id.substring(SB_PREFIX.length());
      try {
        return Long.toString(Long.parseLong(result)); // führende Nullen entfernen
      } catch (Exception e) {
      }
      return result;
    }

    if (id.contains("::")) {
      String result = id.substring(id.indexOf("::") + 2);

      try {
        return Long.toString(Long.parseLong(result)); // führende Nullen entfernen
      } catch (Exception e) {
      }

      if (result.toLowerCase().startsWith(SB_PREFIX)) {
        result = result.substring(SB_PREFIX.length());
        try {
          return Long.toString(Long.parseLong(result)); // führende Nullen entfernen
        } catch (Exception e) {
        }
      }

      return result;
    }

    String nummerNurZiffen = COMPILE.matcher(id).replaceAll("");
    if (isNotBlank(nummerNurZiffen)) {
      try {
        return Long.toString(Long.parseLong(nummerNurZiffen));
      } catch (Exception e) {
        // ignore
      }

      return nummerNurZiffen;
    }

    return id;
  }

  private void addStimmeingabenPolitischeGruppen(Map<GroupVotesSection, Map<String, BigInteger>> candidateVotesPerGroup, Ergebniseingang ergebniseingang) {

    for (Map.Entry<GroupVotesSection, Map<String, BigInteger>> entry : candidateVotesPerGroup.entrySet()) {
      GroupVotesSection groupVotesSection = entry.getKey();
      Map<String, BigInteger> kandidatenStimmen = entry.getValue();
      PolitischeGruppe politischeGruppe = createPolitischeGruppe(groupVotesSection.getRegisteredName());

      String wert = null;
      if (groupVotesSection.getVotes() != null) {
        wert = groupVotesSection.getVotes().toString();
      }

      Stimmeingabe stimmeingabe = addStimmeingabeOhnePosition(ergebniseingang, Stimmenart.LISTENSTIMME, politischeGruppe, wert);
      GruppeImGebiet gruppeImGebiet = stimmeingabe.getGruppeImGebiet();

      Liste liste = new Liste();
      liste.setGruppeImGebiet(gruppeImGebiet);
      liste.setKennwort(politischeGruppe.getSchluessel());

      for (Map.Entry<String, BigInteger> kandidatenEntries : kandidatenStimmen.entrySet()) {
        if (kandidatenEntries.getValue() != null) {
          Long listenkandidaturPosition = bestimmePosition(kandidatenEntries.getKey());
          if (listenkandidaturPosition != null) {
            addListenStimmeingabe(ergebniseingang, gruppeImGebiet, liste, listenkandidaturPosition, kandidatenEntries.getValue().toString());
          }
        }
      }
    }

  }

  @Nullable
  private Long bestimmePosition(String key) {
    try {
      return Long.valueOf(key);
    } catch (Exception e) {
      return null;
    }

  }

  private void addStimmeingabenSystemgruppen(Map<VoteType, BigInteger> validVotes, Ergebniseingang ergebniseingang) {
    Map<SystemGruppe, Gruppe> systemGruppenCache = new HashMap<>();
    for (Map.Entry<VoteType, BigInteger> entry : validVotes.entrySet()) {
      VoteType voteType = entry.getKey();
      BigInteger value = entry.getValue();
      if (voteType != null && value != null && value.longValue() > 0) {
        NlVoteType nlVoteType = NlVoteType.valueOf(voteType.name());
        Stimmenart stimmenart = nlVoteType.getStimmenart();
        Gruppe gruppe = systemGruppenCache.computeIfAbsent(nlVoteType.getSystemGruppe(), x -> createGruppe(nlVoteType.getSystemGruppe()));
        addStimmeingabeOhnePosition(ergebniseingang, stimmenart, gruppe, value.toString());
      }
    }
  }

  @Override
  public Violations validateInputFormat(InputStream is, StimmeingabeImportContext context, FileKonfiguration fileKonfiguration) {
    Wahldurchfuehrung wahl = findWahl(context);

    Violations result = new Violations();
    try {
      byte[] content = IOUtils.toByteArray(is);

      // ELECTVAPP-1037
      try {
        EmlValidator.validateEml510(new ByteArrayInputStream(content));
      } catch (Exception e) {
        LOG.warn("EML invalid: " + e.getMessage(), e);
        result.addWarning("invalid.eml.file");
      }

      EmlWrapper emlWrapper = emlImportMapperHelper.readEmlNoHash(new ByteArrayInputStream(content));
      Eml510Wrapper eml510Wrapper = new Eml510Wrapper(emlWrapper.getContent());

      Eml510 eml510 = Eml510.from(eml510Wrapper.getContent().getId());

      if (!eml510.isGueltigeEml510()) {
        result.addError("eml.510abcd.id.falsch", eml510Wrapper.getContent().getId());
        return result;
      }

      String kennzeichen = wahl.getKennzeichen();

      if (isNotBlank(kennzeichen)) {
        if (eml510Wrapper.getElectionIdentifierStructure() == null) {
          result.addError("eml.import.wahl.id.stimmt.nicht.ueberein", kennzeichen, "");
        } else if (!kennzeichen.equalsIgnoreCase(eml510Wrapper.getElectionIdentifierStructure().getId())) {
          result.addError("eml.import.wahl.id.stimmt.nicht.ueberein", kennzeichen, eml510Wrapper.getElectionIdentifierStructure().getId());
        }
      }

      String expectedWahldatum = new SimpleDateFormat("yyyy-MM-dd").format(wahl.getWahldatum());
      if (!expectedWahldatum.equals(eml510Wrapper.getElectionDate())) {
        result.addError("eml.import.wahl.datum.stimmt.nicht.ueberein", expectedWahldatum, eml510Wrapper.getElectionDate());
      }

      EmlVoteSource emlVoteSource = bestimmeEmlVoteSource(eml510, wahl);
      if (emlVoteSource.isUnbekannt(isDevelopment(wahl.getMandant()))) {
        result.addError("eml.510abcd.vote.source.not.supported", emlWrapper.getContent().getId());
      }

      if (eml510StimmeingabeImportMapperHelper.isSignaturNotwendig(emlWrapper, context)) {
        Object signatureData = context.get(EML_SIGNATUR_AS_BYTES, Object.class);
        boolean ohneSignatur = Boolean.TRUE.equals(context.get(EML_SIGNATUR_OHNE, Boolean.class));

        if (ohneSignatur) {
          result.addWarning("eml.signatur.pruefung.ohne.pruefung");
        }

        if (!(signatureData instanceof byte[])) {
          if (ohneSignatur) {
            result.addWarning("eml.signatur.pruefung.keine.signatur.vorhanden");
          } else {
            result.addError("eml.signatur.pruefung.keine.signatur.vorhanden");
          }
        }

        Gebiet gebiet = bestimmeGebiet(eml510Wrapper, context);

        if (gebiet == null) {
          if (ohneSignatur) {
            result.addWarning("eml.signatur.pruefung.gebiet.nicht.bestimmbar");
          } else {
            result.addError("eml.signatur.pruefung.gebiet.nicht.bestimmbar");
          }
        } else {
          PublicKey publicKey = bestimmePublicKey(gebiet);

          if (publicKey == null) {
            if (ohneSignatur) {
              result.addWarning("eml.signatur.pruefung.kein.pubklikey.vorhanden", gebietNummerFormatter.getDarstellung(gebiet));
            } else {
              result.addError("eml.signatur.pruefung.kein.pubklikey.vorhanden", gebietNummerFormatter.getDarstellung(gebiet));
            }
          } else {
            result.addInfo("eml.signatur.pruefung.pubklikey.vorhanden", gebietNummerFormatter.getDarstellung(gebiet));
          }

          // ELECTVAPP-978
          if (publicKey != null && signatureData instanceof byte[]) {
            try {
              List<NlX509Metadata> nlX509Metadaten = new ArrayList<>();
              //  boolean verified = new Sign(new NlSignConfig()).verify(content, Base64.getDecoder().decode((byte[]) signatur), publicKey);
              boolean verified = new NlCMS().verify(publicKey, (byte[]) signatureData, nlX509Metadaten::add, content);

              if (verified) {
                result.addInfo("eml.signatur.pruefung.valid");
              } else {
                if (ohneSignatur) {
                  result.addWarning("eml.signatur.pruefung.invalid");
                } else {
                  result.addError("eml.signatur.pruefung.invalid");
                }
              }

              for (NlX509Metadata nlX509Metadata : nlX509Metadaten) {
                Date validTill = nlX509Metadata.getValidTill();
                if (!verified || validTill != null && validTill.before(new Date())) {
                  for (Entry<String, String> labelPerValue : EmlSignKeyService.collectNlX509MetadataFormatted(messages, userLocale, nlX509Metadata, "").entrySet()) {
                    result.add(new Violation(ViolationLevel.WARNING, "eml.certificate.import.format.label.value", labelPerValue.getKey(), labelPerValue.getValue()));
                  }
                }
              }
            } catch (Exception e) {
              warn("Fehler beim Prüfen der Signatur: " + e.getMessage(), e);

              if (ohneSignatur) {
                result.addWarning("eml.signatur.pruefung.error");
              } else {
                result.addError("eml.signatur.pruefung.error");
              }
            }
          } else {
            if (ohneSignatur) {
              result.addWarning("eml.signatur.pruefung.nicht.moeglich");
            } else {
              result.addError("eml.signatur.pruefung.nicht.moeglich");
            }
          }
        }
      }
    } catch (IOException e) {
      LOG.warn("Error reading eml: " + e.getMessage(), e);

      result.addError("exception.unknown.message");
    }

    return result;
  }

  private boolean isDevelopment(Mandant mandant) {
    return DevelopmentStore.isDevelopmentStage() || !mandant.getArt().isOhneMandant() && isNotBlank(mandant.getBeschreibung()) && mandant.getBeschreibung().contains("ELECTVAPP-350");
  }

  @Nullable
  private Gebiet bestimmeGebiet(Eml510Wrapper eml510Wrapper, StimmeingabeImportContext context) {
    Wahldurchfuehrung wahl = findWahl(context);

    String gebietName = bestimmeNameGebietTotalVote(eml510Wrapper.getContent());
    String gebietNummerFormatiert = bestimmeNummerGebietTotalVote(eml510Wrapper.getContent());

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = mappeWahldurchfuehrungsgebiet(gebietName, gebietNummerFormatiert, context, bestimmeErfassungsgebietsartenMitGebietsnummer(context),
        bestimmeGebietartErfassungsgebiete(wahl));

    List<Wahldurchfuehrungsgebiet> alleGebiete = wahldurchfuehrungsgebietDAO.findByWahl(wahl);

    return alleGebiete.stream().filter(wahldurchfuehrungsgebietInDb -> wahldurchfuehrungsgebietInDb.getTk().equals(wahldurchfuehrungsgebiet.getTk())).findFirst().orElse(null);
  }

  @Nullable
  private PublicKey bestimmePublicKey(Gebiet gebiet) {
    if (gebiet == null) {
      return null;
    }

    try {
      return gebiet.getCustomData(NlGebietCustomData.class).getPublicKey();
    } catch (Exception e) {
      warn("Public key kann nicht bestimmt werden: " + gebiet + "; " + e.getMessage(), e);

      return null;
    }
  }

  private boolean isErfassungsgebieteVorhanden(Wahldurchfuehrung wahldurchfuehrung) {
    // ELECT-10166
    return wahldurchfuehrungsgebietDAO.findByWahl(wahldurchfuehrung).stream().anyMatch(AbstractWahldurchfuehrungsgebiet::isErfassungsgebiet);
  }

  private boolean isWahlvorschlaegeVorhanden(Wahldurchfuehrung wahldurchfuehrung) {
    // ELECT-10166
    return !wahlvorschlagDAO.findByWahl(wahldurchfuehrung).isEmpty();
  }

  @Override
  public void addProperties(FormBuilder formBuilder, StimmeingabeImportContext context, @Nullable Datei datei) {
    if (datei != null) {
      try {
        try (InputStream is = emlImportMapperHelper.getInputStream(datei)) {
          addProperties(formBuilder, context, is);
        }
      } catch (Exception e) {
        warn("Fehler beim Lesen der Datei: " + datei + ", " + e.getMessage());
      }
    }
  }

  public void addProperties(FormBuilder formBuilder, StimmeingabeImportContext context, InputStream is) {
    EmlWrapper emlWrapper = emlImportMapperHelper.readEmlHash(is, emlImportMapperHelper.getHashCodeProperty(formBuilder, context, ew -> bestimmeEmlValidation(context, ew)));

    if (eml510StimmeingabeImportMapperHelper.isSignaturNotwendig(emlWrapper, context)) {
      // ELECTVAPP-978, keine manuelle Erfasung über die GUI (mehr möglich)...
      // formBuilder.addProperty(new LambdaStringProperty(getMessage("eml.signatur"), false, signatur -> context.put(EML_SIGNATUR, signatur), () -> context.get(EML_SIGNATUR, String.class), 0, Integer.MAX_VALUE));
      // formBuilder.withType(PropertyType.INPUT_TEXTAREA);

      if (isSignaturPruefungOptional(context.getMandant()) /* ELECTVAPP-889 */) {
        formBuilder.addProperty(
            new LambdaBooleanProperty(getMessage("eml.signatur.ohne"), false, signaturOhne -> context.put(EML_SIGNATUR_OHNE, signaturOhne), () -> context.get(EML_SIGNATUR_OHNE, Boolean.class)));
      }
    }
  }

  private boolean isSignaturPruefungOptional(Mandant mandant) {
    return isDevelopment(mandant);
  }

  private EmlValidation bestimmeEmlValidation(StimmeingabeImportContext context, EmlWrapper emlWrapper) {
    // ELECTVAPP-770 immer abfragen

    //    if (NlWahlart.isWasPSB(findWahl(context), currentCustomer.getCustomer())) {
    //      return EmlValidation.BESTAETIGEN; // siehe https://confluence.ivu.de/display/VAPP/EML-Import+validation /  ELECT-10409
    //    }

    // 510d import ist nicht möglich in produktiven Systemen
    if (Eml510.EML510d.getId().equalsIgnoreCase(new Eml510Wrapper(emlWrapper.getContent()).getContent().getId())) {
      return EmlValidation.BESTAETIGEN; // siehe https://confluence.ivu.de/display/VAPP/EML-Import+validation /  ELECT-10409
    }

    return EmlValidation.ABFRAGEN; // im Zweifel mal Abfragen
  }
}
