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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

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

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

import de.ivu.elect.business.datei.entity.DateiImport;
import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisDAO;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.exchange.control.AbstractEntityImporter;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.gebietsbaum.boundary.GebietDAO;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietTK;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gebietsbaum.entity.WahldurchfuehrungsgebietTK;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietDAO;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebietTK;
import de.ivu.elect.business.kandidatur.boundary.AbstractListenkandidaturDAO;
import de.ivu.elect.business.kandidatur.boundary.ListenkandidaturDAO;
import de.ivu.elect.business.kandidatur.control.ListenkandidaturNamenUndListeTK;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturTK;
import de.ivu.elect.business.stimmeingabe.control.ErgebniseingangByEingangssequenznummerComparator;
import de.ivu.elect.business.stimmeingabe.control.ErgebniseingangEingangssequenznummerGenerator;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeCreate;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeImportContext;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeImportExchange;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeImporterHelper;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabePlausiBuilder;
import de.ivu.elect.business.stimmeingabe.control.WahlaufteilenStimmeingabeCreate;
import de.ivu.elect.business.stimmeingabe.control.fiktive.ErgebniseingangPlausiFehlerBeheber;
import de.ivu.elect.business.stimmeingabe.control.plausi.WahlvorschlaegeSupplier;
import de.ivu.elect.business.stimmeingabe.control.sequenznummer.ErgebnissequenznummerSetzenEvent;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestatus;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingangErfasser;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingangMeta;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsherkunft;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeImporterErgebniseingangTK;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeImporterErsetzenStrategie;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeImporterGigContext;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeImporterGruppeImGebietErsetzenStrategie;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeImporterListenkandidaturErsetzenStrategie;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeImporterReferendumOptionErsetzenStrategie;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenDAOFactory;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.system.entity.SystemLogQuelle;
import de.ivu.elect.business.wahl.boundary.WahldurchfuehrungDAO;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlvorschlag.boundary.ReferendumOptionDAO;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagDAO;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOptionTK;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.jee.entity.Meta;
import de.ivu.elect.jee.exchange.ImportOptions;
import de.ivu.elect.jee.exchange.Violations;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;


import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

@Stateless
public class StimmeingabeImporter extends AbstractEntityImporter<Ergebniseingang, StimmeingabeImporterErgebniseingangTK, StimmeingabeImportContext, DateiImportBerichtCustomData> {

  public static final String FILEFORMAT = "FILEFORMATKEY";

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

  public static final String ZUSAMMENGELEGTE_GEBIETE_KEY = "StimmeingabeImporter.ZUSAMMENGELEGTE_GEBIETE_KEY";

  @Inject
  private transient ErgebniseingangMeta ergebniseingangMeta;

  @Inject
  private transient StimmeingabeImportExchange stimmeingabeImportExchange;

  @Inject
  private transient ErgebniseingangDAO ergebniseingangDAO;

  @Inject
  private transient WahldurchfuehrungDAO wahldurchfuehrungDAO;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  private transient WahlvorschlagDAO wahlvorschlagDAO;

  @Inject
  private transient GruppeImGebietDAO gruppeImGebietDAO;

  @Inject
  private transient ListenkandidaturDAO listenkandidaturDAO;

  @Inject
  private transient ReferendumOptionDAO referendumOptionDAO;

  @Inject
  private transient StimmeingabeDAO stimmeingabeDAO;

  @Inject
  private transient StimmeingabefehlerDAO stimmeingabefehlerDAO;

  @Inject
  private transient ErgebnissequenznummerSetzenEvent ergebnissequenznummerSetzenEvent;

  @Inject
  private transient StimmeingabeCreate stimmeingabeCreate;

  @Inject
  private transient StimmeingabeErfassenDescriptionResolver stimmeingabeErfassenDescriptionResolver;

  @Inject
  private transient ErgebniseingangPlausiFehlerBeheber ergebniseingangPlausiFehlerBeheber;

  @Inject
  private transient ErgebniseingangEingangssequenznummerGenerator ergebniseingangEingangssequenznummerGenerator;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient GebietDAO gebietDAO;

  @Inject
  private transient StimmeingabeImporterHelper stimmeingabeImporterHelper;

  @Inject
  private transient WahlaufteilenStimmeingabeCreate wahlaufteilenStimmeingabeCreate;

  @Inject
  private transient EntityAufStimmzettelDatenDAOFactory entityAufStimmzettelDatenDAOFactory;

  @Inject
  private transient ErgebniseingangStatus ergebniseingangStatus;

  @Inject
  private transient SystemLogWriter systemLogWriter;

  @Inject
  private transient ErgebnisDAO ergebnisDAO;

  public StimmeingabeImporter() {
    super();
  }

  @Override
  protected void postImportActions(List<Ergebniseingang> ergebniseingangs, StimmeingabeImportContext context, ImportOptions importOptions, DateiImportBerichtCustomData customDateiImportBericht, StringBuilder bericht) {
    super.postImportActions(ergebniseingangs, context, importOptions, customDateiImportBericht, bericht);
    ergebnissequenznummerSetzenEvent.sendChangeEvent();
  }

  @Override
  protected List<Ergebniseingang> readImportObject(DateiImport dateiImport, StimmeingabeImportContext context) throws MappingException {
    // Abkürzung für de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeImporter.getStimmeingabeImporterGruppeImGebietErsetzenStrategie
    context.put(FILEFORMAT, dateiImport.getDatei().getFileFormat());

    if (dateiImport.getDatei() != null) {
      context.setOriginalDateiname(dateiImport.getDatei().getOriginalDateiname());
    }

    return super.readImportObject(dateiImport, context);
  }

  @Override
  public void persistiere(Collection<Ergebniseingang> neueEntities, StimmeingabeImportContext context, DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    var wahldurchfuehrung = findWahl(context);

    var gebieteInDB = getGebieteInDB(wahldurchfuehrung, neueEntities);

    var gruppenImGebietInWahl = ladeGruppeImGebiet(wahldurchfuehrung, neueEntities);
    var gruppeImGebietInWahlPerTk = GruppeImGebietTK.mappeGruppeImGebietNachTK(gruppenImGebietInWahl);

    Collection<Listenkandidatur> listenkandidaturen = ladeListenkandidaturen(neueEntities, wahldurchfuehrung);

    var listenkandidaturenInDbNachListenkandidaturTk = ListenkandidaturTK.mappeListenkandidaturNachTK(listenkandidaturen);

    var listenkandidaturenInDbNachNamenUndListeTk = ListenkandidaturNamenUndListeTK.mappeListenkandidaturNachTK(listenkandidaturen);

    Map<Listenkandidatur, ListenkandidaturTK> listenkandidaturAusDateiTkCache = new HashMap<>();

    var referendumOptionenInDb = ReferendumOptionTK.mappeReferendumOptionNachTK(referendumOptionDAO.findByWahl(wahldurchfuehrung));

    Map<ReferendumOption, ReferendumOptionTK> referendumOptionAusDateiTKCache = new HashMap<>();

    var gigContext = getGruppeImGebietContext(wahldurchfuehrung, gruppenImGebietInWahl);

    var wahlvorschlaege = new WahlvorschlaegeSupplier(wahldurchfuehrung, wahlvorschlagDAO);

    List<Ergebniseingang> neueEntitiesSortiert = new ArrayList<>(neueEntities);
    neueEntitiesSortiert.sort(ErgebniseingangByEingangssequenznummerComparator.ASCENDING);

    var gigNachGebiet = gruppenImGebietInWahl.stream().filter(gig -> gig.getGebiet() != null).collect(groupingBy(GruppeImGebiet::getGebiet));

    var stimmeingabeImporterGruppeImGebietErsetzenStrategie = getStimmeingabeImporterGruppeImGebietErsetzenStrategie(context);
    var stimmeingabeImporterListenkandidaturErsetzenStrategie = getStimmeingabeImporterListenkandidaturErsetzenStrategie(context);
    var stimmeingabeImporterReferendumOptionErsetzenStrategie = getStimmeingabeImporterReferendumOptionErsetzenStrategie(context);

    for (var ergebniseingang : neueEntitiesSortiert) {
      var eingangSpeichernn = tauscheWahldurchfuehrungsgebietUndSetzeErfasstIn(ergebniseingang, gebieteInDB);

      gebieteZusammenErfassen(ergebniseingang, gebieteInDB);

      if (eingangSpeichernn) {
        Set<Stimmeingabe> nichtZuPersistierenListenkandidaturStimmen = new HashSet<>();
        for (var stimmeingabe : ergebniseingang.getStimmeingaben()) {
          stimmeingabeImporterGruppeImGebietErsetzenStrategie.tauscheGruppeImGebiet(wahldurchfuehrung, stimmeingabe, gruppeImGebietInWahlPerTk, gigContext,
              context, stimmeingabeImporterHelper);
          var listenkandidaturVorhanden = stimmeingabe.getListenkandidatur() != null;
          stimmeingabeImporterListenkandidaturErsetzenStrategie.tauscheListenKandidatur(stimmeingabe, listenkandidaturenInDbNachListenkandidaturTk,
              listenkandidaturenInDbNachNamenUndListeTk, listenkandidaturAusDateiTkCache, gruppeImGebietInWahlPerTk, gigContext, stimmeingabeImporterHelper,
              context.getUmgangNichtZugelasseneKandidaten());
          if (listenkandidaturVorhanden && stimmeingabe.getListenkandidatur() == null) {
            // Listenkandidtur nicht gefunden, optional, siehe Validierung
            nichtZuPersistierenListenkandidaturStimmen.add(stimmeingabe);
          }

          stimmeingabeImporterReferendumOptionErsetzenStrategie.tauscheReferendumOption(stimmeingabe, referendumOptionenInDb, referendumOptionAusDateiTKCache,
              gruppeImGebietInWahlPerTk, gigContext, stimmeingabeImporterHelper);
        }

        if (!nichtZuPersistierenListenkandidaturStimmen.isEmpty()) {
          Set<Stimmeingabe> zuLoeschendeStimmeingaben = new HashSet<>(nichtZuPersistierenListenkandidaturStimmen);

          ergebniseingang.setErgebniseingangsfehlerstatus(null); // Auf jeden Fall plausibislisieren

          if (context.getUmgangFehlendeKandidaten().isAufWahlvorschlagAufteilen() && isSupportsListenstimmenAufWahlvorschlaegeAufteilen(wahldurchfuehrung)) {
            stimmenAufWahlvorschlagAufteilen(wahldurchfuehrung, nichtZuPersistierenListenkandidaturStimmen, ergebniseingang, zuLoeschendeStimmeingaben);
          }

          LOG.info("Speichere nicht: " + zuLoeschendeStimmeingaben);

          for (var stimmeingabe : zuLoeschendeStimmeingaben) {
            ergebniseingang.removeStimmeingabe(stimmeingabe);
          }
        }

        if (ergebniseingang.getErgebniseingangsfehlerstatus() == null) {
          var stimmeingabeErfassenDescription = getStimmeingabeErfassenDescriptionByWahl(wahldurchfuehrung);

          if (context.isPlausibilisierungKannFehlerBestaetigen()) {
            // WASDE-984
            var ergebniseingangPersisted = ergaenzeUndPlausibilisiereUndSpeichereErgebniseingangKannFehlerIgnorieren(ergebniseingang, gigNachGebiet,
                stimmeingabeErfassenDescription, wahlvorschlaege, context);

            bericht.append(getMessage("stimmeingabe.import.ergebnis.plausibilisierung.kann.fehler.bestaetigt",
                gebietNummerFormatter.getNameMitNummer(ergebniseingangPersisted.getGebiet()),
                getMessage(ergebniseingangPersisted.getErgebniseingangsfehlerstatus())));
            bericht.append("\n");
          } else {
            var ergebniseingangPersisted = ergaenzeUndPlausibilisiereUndSpeichereErgebniseingangKeinFehlerIgnorieren(ergebniseingang, gigNachGebiet,
                stimmeingabeErfassenDescription, wahlvorschlaege, context);

            bericht.append(
                getMessage("stimmeingabe.import.ergebnis.plausibilisierung", gebietNummerFormatter.getNameMitNummer(ergebniseingangPersisted.getGebiet()),
                    getMessage(ergebniseingangPersisted.getErgebniseingangsfehlerstatus())));
            bericht.append("\n");
          }

        } else {
          bericht.append(getMessage("stimmeingabe.import.ergebnis.plausibilisierung.vorhanden", getMessage(ergebniseingang.getErgebniseingangsfehlerstatus())));
          bericht.append("\n");

          speicherePlausibilisiertenErgebniseingang(ergebniseingang);
        }
      }
    }
  }

  protected void stimmenAufWahlvorschlagAufteilen(Wahldurchfuehrung wahldurchfuehrung, Set<Stimmeingabe> nichtZuPersistierenListenkandidaturStimmen, Ergebniseingang ergebniseingang, Set<Stimmeingabe> zuLoeschendeStimmeingaben) {
    // nichts zu tun
  }

  protected boolean isSupportsListenstimmenAufWahlvorschlaegeAufteilen(Wahl wahl) {
    return false;
  }

  private List<Listenkandidatur> ladeListenkandidaturen(Collection<Ergebniseingang> neueEntities, Wahldurchfuehrung wahldurchfuehrung) {
    List<Listenkandidatur> listenkandidaturen = new ArrayList<>();
    if (isListenkandidaturStimmenVorhanden(neueEntities)) {
      // ELECTKOM-2626
      if (isOptimiereGebietLaden(neueEntities)) {
        addListenkandidaturenFuerGebiet(listenkandidaturen, getPersistentesGebietAusErgebniseingang(wahldurchfuehrung, neueEntities), listenkandidaturDAO);
      }
      // fallback
      if (listenkandidaturen.isEmpty()) {
        listenkandidaturen = listenkandidaturDAO.findByWahl(wahldurchfuehrung);
      }
    }
    return listenkandidaturen;
  }

  public static void addListenkandidaturenFuerGebiet(List<Listenkandidatur> listenkandidaturen, Gebiet gebiet, AbstractListenkandidaturDAO listenkandidaturDAO) {
    if (gebiet == null) {
      return;
    }
    listenkandidaturen.addAll(listenkandidaturDAO.findByGebiet(gebiet));
    addListenkandidaturenFuerGebiet(listenkandidaturen, gebiet.getUebergeordnetesGebiet(), listenkandidaturDAO);
  }

  protected void gebieteZusammenErfassen(Ergebniseingang ergebniseingang, Map<GebietTK, Wahldurchfuehrungsgebiet> gebieteInDB) {
    // ELECTKOM-1471 Spzialfall RP

    var erfasstGebiete = getZusammengelegteGebiete(ergebniseingang);
    var gebietErgebniseingang = ergebniseingang.getGebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity();
    LOG.info("gebieteZusammenErfassen: ergebniseingang=" + ergebniseingang + ", gebiet=" + gebietErgebniseingang + ", " + erfasstGebiete.size());
    if (!erfasstGebiete.isEmpty()) {
      var ergebnis = findeErgebnis(ergebniseingang.getGebiet(), ergebniseingang.getErgebniseingangsart().korrespondierendeErgebnisart());
      if (ergebnis != null) {
        LOG.info("gebieteZusammenErfassen: ergebnis=" + ergebnis);
        for (var anderesGebiet : erfasstGebiete) {
          LOG.info("gebieteZusammenErfassen: anderesGebiet=" + anderesGebiet + ", " + anderesGebiet.isPersistent());
          var anderesGebietInDb = gebieteInDB.get(anderesGebiet.getGebietTK());
          LOG.info("gebieteZusammenErfassen: anderesGebietInDb=" + anderesGebietInDb);
          if (anderesGebietInDb != null && !gebietErgebniseingang.equals(anderesGebiet)) {
            var ergebniseingabestatus = ergebniseingangStatus.setzeGebietErgebniseingabestatus(anderesGebietInDb, Ergebniseingabestatus.AUSBLEIBEND, ergebnis,
                gebietErgebniseingang);
            LOG.info("gebieteZusammenErfassen: ergebniseingabestatus=" + ergebniseingabestatus);
            if (!ergebniseingabestatus.isAusbleibend()) {
              systemLogWriter.writeErrorLogMessageFuerMandant(ergebniseingang, "Gebiet nicht zusammengelegt:" + anderesGebiet + ", " + gebietErgebniseingang,
                  SystemLogQuelle.STIMMEINGABE_DATEIEN_IMPORTER);
            }
          }
        }
      }

    }
  }

  protected List<Wahldurchfuehrungsgebiet> getZusammengelegteGebiete(Ergebniseingang ergebniseingang) {
    var o = ergebniseingang.getTransientContext().get(ZUSAMMENGELEGTE_GEBIETE_KEY);
    if (o == null) {
      return Collections.emptyList();
    }
    return (List<Wahldurchfuehrungsgebiet>) o;
  }

  @Nullable
  private Ergebnis findeErgebnis(Gebiet gebiet, Ergebnisart ergebnisart) {
    return ergebnisDAO.findByWahlAndErgebnisart(gebiet.getWahl(), ergebnisart).stream().findFirst().orElse(null);
  }

  private boolean isListenkandidaturStimmenVorhanden(Collection<Ergebniseingang> neueEntities) {
    return neueEntities.stream().flatMap(neueEntity -> neueEntity.getStimmeingabesByErgebniseingang().stream())
        .anyMatch(stimmeingabe -> stimmeingabe.getListenkandidatur() != null);
  }

  protected Map<GebietTK, Wahldurchfuehrungsgebiet> getGebieteInDB(Wahldurchfuehrung wahldurchfuehrung, Collection<Ergebniseingang> ergebniseingangs) {
    return WahldurchfuehrungsgebietTK.mappeGebietNachTK(ladeGebieteDerWahl(wahldurchfuehrung, ergebniseingangs));
  }

  protected StimmeingabeErfassenDescription getStimmeingabeErfassenDescriptionByWahl(Wahldurchfuehrung wahldurchfuehrung) {
    return stimmeingabeErfassenDescriptionResolver.getStimmeingabeErfassenDescriptionByWahl(wahldurchfuehrung);
  }

  private void speicherePlausibilisiertenErgebniseingang(Ergebniseingang ergebniseingang) {
    var naechsteEingangssequenznummer = ergebniseingangEingangssequenznummerGenerator.getNaechsteEingangssequenznummer();

    if (ergebniseingang.getEingangssequenznummer() == null || ergebniseingang.getEingangssequenznummer() < naechsteEingangssequenznummer) {
      ergebniseingang.setEingangssequenznummer(naechsteEingangssequenznummer);
    }

    if (ergebniseingang.getEingangssequenznummer() > naechsteEingangssequenznummer) {
      ergebniseingangEingangssequenznummerGenerator.erhoeheSequenznummerWennNoetig(ergebniseingang.getEingangssequenznummer());
    }

    ergebniseingangDAO.persist(ergebniseingang);

    for (var stimmeingabe : ergebniseingang.getStimmeingaben()) {
      stimmeingabeDAO.persist(stimmeingabe);

      for (var stimmeingabefehler : stimmeingabe.getStimmeingabefehler()) {
        stimmeingabefehlerDAO.persist(stimmeingabefehler);
      }
    }

    stimmeingabeCreate.exportiereStimmeingaben(ergebniseingang);

    // ELECT-8452
    ergebniseingang.setOverrideErgebniseingangsart(true);
    wahlaufteilenStimmeingabeCreate.saveFuerZielWahl(ergebniseingang);

  }

  private Ergebniseingang ergaenzeUndPlausibilisiereUndSpeichereErgebniseingangKeinFehlerIgnorieren(Ergebniseingang ergebniseingangAusImport, Map<Gebiet, List<GruppeImGebiet>> gigNachGebiet, StimmeingabeErfassenDescription stimmeingabeErfassenDescription, Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> wahlvorschlaege, StimmeingabeImportContext context) {

    var result = ergaenzeUndErzeugeStimmeingabePlausis(ergebniseingangAusImport, gigNachGebiet, stimmeingabeErfassenDescription, wahlvorschlaege, context);

    // TODO: Funktioniert nicht im Zusammenhang mit Löschen vorhandener Eingänge
    speichereErgebniseingangMitStimmeingabePlausibilisierung(result, ergebniseingangAusImport,
        ergebniseingang -> stimmeingabeCreate.saveOhneLetzterErgebniseingangInGebietPruefungMitStimmeingabePlausibilisierung(result, null,
            StimmeingabePlausiBuilder.NO_OP_BUILDER));

    return result;
  }

  private Ergebniseingang ergaenzeUndPlausibilisiereUndSpeichereErgebniseingangKannFehlerIgnorieren(Ergebniseingang ergebniseingangAusImport, Map<Gebiet, List<GruppeImGebiet>> gigNachGebiet, StimmeingabeErfassenDescription stimmeingabeErfassenDescription, Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> wahlvorschlaege, StimmeingabeImportContext context) {
    var result = ergaenzeUndErzeugeStimmeingabePlausis(ergebniseingangAusImport, gigNachGebiet, stimmeingabeErfassenDescription, wahlvorschlaege, context);

    speichereErgebniseingangMitStimmeingabePlausibilisierung(result, ergebniseingangAusImport,
        ergebniseingang -> stimmeingabeCreate.saveOhneLetzterErgebniseingangInGebietPruefungMitStimmeingabePlausibilisierungKannFehlerBestaetigen(
            ergebniseingang, null, StimmeingabePlausiBuilder.NO_OP_BUILDER));

    return result;
  }

  private Ergebniseingang ergaenzeUndErzeugeStimmeingabePlausis(Ergebniseingang ergebniseingangAusImport, Map<Gebiet, List<GruppeImGebiet>> gigNachGebiet, StimmeingabeErfassenDescription stimmeingabeErfassenDescription, Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> wahlvorschlaege, StimmeingabeImportContext context) {
    var gruppenImGebietFuerErgebniseingang = getGruppenImGebietByErgebniseingang(gigNachGebiet, ergebniseingangAusImport);

    var result = createErgebniseingangZumPersistieren(ergebniseingangAusImport, context);

    fuelleStimmeingaben(ergebniseingangAusImport, stimmeingabeErfassenDescription, wahlvorschlaege, gruppenImGebietFuerErgebniseingang, result);
    stimmeingabeErfassenDescription.getStimmeingabePlausiBuilder().fillStimmeingabePlausis(result);

    ergebniseingangPlausiFehlerBeheber.behebePlausiFehlerByAuffuellenLeererStimmeingabenMitSollWerten(result,
        stimmeingabe -> !stimmeingabe.hasPolitischeGruppe()/*ELECTKOM-1657*/);

    return result;
  }

  protected void fuelleStimmeingaben(Ergebniseingang ergebniseingangAusImport, StimmeingabeErfassenDescription stimmeingabeErfassenDescription, Supplier<Map<Gruppe, Collection<Wahlvorschlag>>> wahlvorschlaege, Collection<GruppeImGebiet> gruppenImGebietFuerErgebniseingang, Ergebniseingang result) {
    stimmeingabeErfassenDescription.buildStimmeingabeErfassenModels(
        new DefaultStimmeingabeErfassenContext(result, ergebniseingangAusImport, gruppenImGebietFuerErgebniseingang, wahlvorschlaege,
            getEntityAufStimmzettelDaten(), wahldurchfuehrungsgebietDAO::findByErfasstFuerGebiete));
  }

  protected void speichereErgebniseingangMitStimmeingabePlausibilisierung(Ergebniseingang ergebniseingang, Ergebniseingang ergebniseingangAusImport, Consumer<Ergebniseingang> stimmeingabeCreateFct) {
    stimmeingabeCreateFct.accept(ergebniseingang);
  }

  // Ein neuer Ergebniseingang, in den der ModelBuilder Stimmeingaben erzeugt, und der später plausibilisiert wird.
  protected Ergebniseingang createErgebniseingangZumPersistieren(Ergebniseingang ergebniseingang, StimmeingabeImportContext context) {
    var result = new Ergebniseingang();
    ergebniseingang.copyEntityAttributesUsingSetter(result);

    result.setWahldurchfuehrungsgebiet(ergebniseingang.getWahldurchfuehrungsgebiet());

    var wahldurchfuehrung = findWahl(context);

    // defaults
    if (result.getErgebniseingangsart() == null && wahldurchfuehrung != null) {
      // Falls ImportMapper keine Ergebniseingangsart liefert, aktuelle Ergebniseingangsart der Wahl nehmen
      result.setErgebniseingangsart(wahldurchfuehrung.getErgebniseingangsart());
    }

    if (result.getErgebniseingangsherkunft() == null) {
      result.setErgebniseingangsherkunft(Ergebniseingangsherkunft.IMPORT);
    }

    if (isBlank(result.getErfasser())) {
      result.setErfasser(ErgebniseingangErfasser.benutzerOrDefault().getKennung());
    }

    return result;
  }

  private Collection<GruppeImGebiet> getGruppenImGebietByErgebniseingang(Map<Gebiet, List<GruppeImGebiet>> gigNachGebiet, Ergebniseingang ergebniseingang) {
    Collection<GruppeImGebiet> result = new ArrayList<>();

    if (gigNachGebiet.containsKey(ergebniseingang.getWahldurchfuehrungsgebiet())) {
      result.addAll(gigNachGebiet.get(ergebniseingang.getWahldurchfuehrungsgebiet()));
    }

    return result;
  }

  protected boolean tauscheWahldurchfuehrungsgebietUndSetzeErfasstIn(Ergebniseingang ergebniseingang, Map<GebietTK, Wahldurchfuehrungsgebiet> gebieteInDb) {
    var wahldurchfuehrungsgebietInDatei = ergebniseingang.getWahldurchfuehrungsgebiet();
    var gebietTK = wahldurchfuehrungsgebietInDatei.getGebietTK();
    var wahldurchfuehrungsgebietInDb = gebieteInDb.get(gebietTK);
    if (wahldurchfuehrungsgebietInDb == null) {
      // ELECTKOM-2645
      return false;
    }

    ergebniseingang.setWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietInDb);

    var wahldurchfuehrung = wahldurchfuehrungsgebietInDb.getWahl().castTo(Wahldurchfuehrung.class).getEntity();

    if (wahldurchfuehrungsgebietInDatei.getErgebniseingabestatus(
        wahldurchfuehrung.getErgebniseingangsart()) != null && wahldurchfuehrungsgebietInDatei.getErgebniseingabestatus(
        wahldurchfuehrung.getErgebniseingangsart()).isAusbleibend() || wahldurchfuehrungsgebietInDatei.getErfasstIn() != null) {

      var ergebnis = findeErgebnis(wahldurchfuehrungsgebietInDb, wahldurchfuehrung.getErgebniseingangsart().korrespondierendeErgebnisart());
      if (ergebnis == null) {
        throw new IllegalStateException(
            "Ergebnis nicht gefunden: " + wahldurchfuehrungsgebietInDb + ", " + wahldurchfuehrung.getErgebniseingangsart().korrespondierendeErgebnisart());
      } else {
        Wahldurchfuehrungsgebiet erfasstInInDb = null;
        if (wahldurchfuehrungsgebietInDatei.getErfasstIn() != null) {
          erfasstInInDb = gebieteInDb.get(wahldurchfuehrungsgebietInDatei.getErfasstIn().getWahldurchfuehrungsgebietTK());
        }
        var ergebniseingabestatus = ergebniseingangStatus.setzeGebietErgebniseingabestatus(wahldurchfuehrungsgebietInDb, Ergebniseingabestatus.AUSBLEIBEND,
            ergebnis, erfasstInInDb);
        if (ergebniseingabestatus.isAusbleibend()) {
          return false;
        } else {
          throw new IllegalStateException("Gebiet nicht ausbleibend: " + wahldurchfuehrungsgebietInDb + ", " + ergebniseingabestatus);
        }
      }
    }
    return true;
  }

  @Override
  protected void aktualisiere(Ergebniseingang entityInDatei, Ergebniseingang entityInDatenbank, StimmeingabeImportContext context, DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    throw new UnsupportedOperationException();// Bereits erfasste Stimmeingaben werden nicht aktualisiert...nur angehängt
  }

  @Override
  protected void loescheAusDatenbank(Collection<Ergebniseingang> entitiesInDb, StimmeingabeImportContext context, DateiImportBerichtCustomData dateiImportBerichtCustomData, StringBuilder bericht) {
    throw new UnsupportedOperationException(); // Bereits erfasste Stimmeingaben werden nicht aus der Datenbank gelöscht...nur angehängt
  }

  @Override
  protected List<Ergebniseingang> getAllEntitiesInDB(StimmeingabeImportContext context) {
    return new ArrayList<>(); // die Daten in der Datenbank interessieren nicht, sie werden nicht aktualisiert und auch nicht gelöscht...
  }

  @Override
  protected Function<Ergebniseingang, StimmeingabeImporterErgebniseingangTK> getEntityToEntityTKFunction() {
    return StimmeingabeImporterErgebniseingangTK::new;

  }

  @Override
  protected void validiere(Violations violations, List<Ergebniseingang> ergebniseingangs, StimmeingabeImportContext context, ImportOptions importOptions) {
    var wahldurchfuehrung = findWahl(context);

    var gebieteDerWahl = ladeGebieteDerWahl(wahldurchfuehrung, ergebniseingangs);
    var gebieteInDB = WahldurchfuehrungsgebietTK.mappeWahldurchfuehrungsgebietNachTK(gebieteDerWahl);
    Set<Wahldurchfuehrungsgebiet> betrachteteGebiete = new HashSet<>();
    var gruppenImGebietInWahl = ladeGruppeImGebiet(wahldurchfuehrung, ergebniseingangs);
    var gruppeImGebietInDb = GruppeImGebietTK.mappeGruppeImGebietNachTK(gruppenImGebietInWahl);
    var listenkandidaturenInDb = ladeListenkandidaturen(ergebniseingangs, wahldurchfuehrung);

    var listenkandidaturenInDbNachListenkandidaturTk = ListenkandidaturTK.mappeListenkandidaturNachTK(listenkandidaturenInDb);

    Map<Listenkandidatur, Listenkandidatur> betrachteteListenkandidaturen = new HashMap<>();
    var listenkandidaturenInDbNachNamenUndListeTk = ListenkandidaturNamenUndListeTK.mappeListenkandidaturNachTK(listenkandidaturenInDb);

    var referendumOptionenInDb = ReferendumOptionTK.mappeReferendumOptionNachTK(referendumOptionDAO.findByWahl(wahldurchfuehrung));
    Set<ReferendumOption> betrachteteReferendumOptionen = new HashSet<>();

    var gigInDbContext = getGruppeImGebietContext(wahldurchfuehrung, gruppenImGebietInWahl);

    var stimmeingabeImporterGruppeImGebietErsetzenStrategie = getStimmeingabeImporterGruppeImGebietErsetzenStrategie(context);
    var stimmeingabeImporterListenkandidaturErsetzenStrategie = getStimmeingabeImporterListenkandidaturErsetzenStrategie(context);
    var stimmeingabeImporterReferendumOptionErsetzenStrategie = getStimmeingabeImporterReferendumOptionErsetzenStrategie(context);

    for (var ergebniseingang : ergebniseingangs) {
      // ELECT-4143 deaktiviert, da eigene Seite zum Upload von F-Dateien
      // pruefeRechteFuerGebietBearbeitenVorhanden(violations, gebieteInDB, ergebniseingang, context);

      pruefeGebietVorhanden(violations, gebieteInDB, betrachteteGebiete, ergebniseingang, context);

      var wahldurchfuehrungsgebietInDatei = ergebniseingang.getWahldurchfuehrungsgebiet();
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb = null;
      if (wahldurchfuehrungsgebietInDatei != null) {
        wahldurchfuehrungsgebietInDb = gebieteInDB.get(wahldurchfuehrungsgebietInDatei.getWahldurchfuehrungsgebietTK());

        if (wahldurchfuehrungsgebietInDb != null) {
          var ergebniseingabestatus = wahldurchfuehrungsgebietInDatei.getErgebniseingabestatus(context.getWahldurchfuehrung().getErgebniseingangsart());
          if (ergebniseingabestatus != null && ergebniseingabestatus.isAusbleibend()) {
            if (wahldurchfuehrungsgebietInDb.getErgebniseingabestatus(wahldurchfuehrung.getErgebniseingangsart())
                .isAusbleibend() || ergebniseingangStatus.isAusbleibendMoeglich(wahldurchfuehrungsgebietInDb)) {
              violations.addInfo("stimmeingabe.import.gebiet.ausbleibend", gebietNummerFormatter.getDarstellung(wahldurchfuehrungsgebietInDb));
            } else {
              violations.addWarning("stimmeingabe.import.gebiet.ausbleibend.nicht.moeglich",
                  gebietNummerFormatter.getDarstellung(wahldurchfuehrungsgebietInDb));
            }
          }

          if (wahldurchfuehrungsgebietInDatei.getErfasstIn() != null) {
            var wahldurchfuehrungsgebietErfasstInInDb = gebieteInDB.get(wahldurchfuehrungsgebietInDatei.getErfasstIn().getWahldurchfuehrungsgebietTK());
            if (wahldurchfuehrungsgebietErfasstInInDb == null) {
              violations.addWarning("stimmeingabe.import.gebiet.erfasst.in.nicht.gefunden", gebietNummerFormatter.getDarstellung(wahldurchfuehrungsgebietInDb),
                  wahldurchfuehrungsgebietInDatei.getErfasstIn().getWahldurchfuehrungsgebietTK().toStringLokalisiert(getMessages()));
            } else {
              violations.addInfo("stimmeingabe.import.gebiet.erfasst.in", gebietNummerFormatter.getDarstellung(wahldurchfuehrungsgebietInDb),
                  gebietNummerFormatter.getDarstellung(wahldurchfuehrungsgebietErfasstInInDb));
            }
          }

          Set<Listenkandidatur> persistenteListenKandidaturenImErgebniseingang = new HashSet<>();

          for (var stimmeingabe : ergebniseingang.getStimmeingaben()) {
            stimmeingabeImporterGruppeImGebietErsetzenStrategie.pruefeGruppeImGebiet(wahldurchfuehrung, violations, gruppeImGebietInDb, stimmeingabe,
                gigInDbContext, context, stimmeingabeImporterHelper, gebietNummerFormatter);

            var listenkandidatur = pruefeListenkandidatur(violations, wahldurchfuehrung, wahldurchfuehrungsgebietInDb,
                listenkandidaturenInDbNachListenkandidaturTk, listenkandidaturenInDbNachNamenUndListeTk, betrachteteListenkandidaturen, stimmeingabe,
                stimmeingabeImporterListenkandidaturErsetzenStrategie, gruppeImGebietInDb, gigInDbContext, stimmeingabeImporterHelper, context);

            if (listenkandidatur != null && !persistenteListenKandidaturenImErgebniseingang.add(listenkandidatur)) {
              violations.addWarning("stimmeingabe.import.listenkandidatur.mehrfach", listenkandidatur.getVorgegebenePosition(),
                  getGruppenposition(listenkandidatur), gebietNummerFormatter.getDarstellung(wahldurchfuehrungsgebietInDb));
            }

            if (stimmeingabe.getListenkandidatur() != null && listenkandidatur == null && context.getUmgangFehlendeKandidaten()
                .isAufWahlvorschlagAufteilen() && !isSupportsListenstimmenAufWahlvorschlaegeAufteilen(wahldurchfuehrungsgebietInDb.getWahl())) {
              violations.addError("stimmeingabe.import.listenkandidatur.stimmen.aufteilen.nicht.moeglich");
            }

            pruefeReferendumOptionVorhanden(violations, referendumOptionenInDb, betrachteteReferendumOptionen, stimmeingabe,
                stimmeingabeImporterReferendumOptionErsetzenStrategie, gruppeImGebietInDb, gigInDbContext, stimmeingabeImporterHelper);
          }
        }
      }
    }
  }

  protected List<Wahldurchfuehrungsgebiet> ladeGebieteDerWahl(Wahldurchfuehrung wahldurchfuehrung, /* Optimierung für BB ELECTBB-217 */ Collection<Ergebniseingang> ergebniseingangs) {
    // ELECTBB-217 Optimierung bei der Replikation, nicht alle Gebiete (2.400) laden sondern nur das eine
    if (isOptimiereGebietLaden(ergebniseingangs)) {
      try {
        return Collections.singletonList(
            wahldurchfuehrungsgebietDAO.findByPrimaryKey(getPersistentesGebietAusErgebniseingang(wahldurchfuehrung, ergebniseingangs).getPk()));
      } catch (NoResultException nre) {
        // Gebiet aus Ergebniseingang nicht vorhanden => fallback, doch alles laden
      }
    }
    return wahldurchfuehrungsgebietDAO.findByWahl(wahldurchfuehrung);
  }

  protected List<GruppeImGebiet> ladeGruppeImGebiet(Wahldurchfuehrung wahldurchfuehrung, /* Optimierung für BB ELECTBB-217 */ Collection<Ergebniseingang> ergebniseingangs) {
    // ELECTBB-217 Optimierung bei der Replikation, nicht alle Gruppe im Gebiet laden sondern nur die vom Gebiet /* ? notwendig und alle übergeordnenten Gebiete */ => es werden ca. 100 GIGs statt 80.000 geladen
    if (isOptimiereGebietLaden(ergebniseingangs)) {
      try {
        return new ArrayList<>(gruppeImGebietDAO.findByGebiet(getPersistentesGebietAusErgebniseingang(wahldurchfuehrung, ergebniseingangs)));
      } catch (NoResultException e) {
        // Gebiet aus Ergebniseingang nicht vorhanden => fallback, doch alles laden
      }
    }

    return gruppeImGebietDAO.findByWahl(wahldurchfuehrung);
  }

  // ELECTBB-217 Optimierung bei der Replikation, nicht alle Gruppe im Gebiet laden sondern nur die vom Gebiet /* ? notwendig und alle übergeordnenten Gebiete */ => es werden ca. 100 GIGs statt 80.000 geladen
  protected boolean isOptimiereGebietLaden(Collection<Ergebniseingang> ergebniseingangs) {
    return ergebniseingangs.size() == 1 && isNotBlank(
        ergebniseingangs.iterator().next().getGebiet().getNummer()) /* ELECTKOM-180 Nummer nicht gesetzt*/ && getZusammengelegteGebiete(
        ergebniseingangs.iterator().next()).isEmpty() /*ELECTKOM-1664*/ && !isErfasstInGebietVorhanden(ergebniseingangs);
  }

  private boolean isErfasstInGebietVorhanden(Collection<Ergebniseingang> ergebniseingangs) {
    return ergebniseingangs.stream().anyMatch(
        ergebniseingang -> ergebniseingang.getGebiet() != null && ergebniseingang.getGebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity()
            .getErfasstIn() != null);
  }

  // Für Optimierungen
  protected StimmeingabeImporterGigContext getGruppeImGebietContext(Wahldurchfuehrung wahldurchfuehrung, List<GruppeImGebiet> gruppenImGebietInWahl) {
    return new StimmeingabeImporterGigContext(gruppenImGebietInWahl, wahlvorschlagDAO.findByWahl(wahldurchfuehrung), getEntityAufStimmzettelDaten());
  }

  @Nullable
  private Listenkandidatur pruefeListenkandidatur(Violations violations, Wahldurchfuehrung wahldurchfuehrung, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb, Map<ListenkandidaturTK, Listenkandidatur> listenkandidaturenInDbNachListenkandidaturTk, Map<ListenkandidaturNamenUndListeTK, Listenkandidatur> listenkandidaturenInDbNachNamenUndListeTk, Map<Listenkandidatur, Listenkandidatur> betrachteteListenkandidaturen, Stimmeingabe stimmeingabe, StimmeingabeImporterListenkandidaturErsetzenStrategie stimmeingabeImporterListenkandidaturErsetzenStrategie, Map<GruppeImGebietTK, GruppeImGebiet> gruppeImGebietInDb, StimmeingabeImporterGigContext gigInDbContext, StimmeingabeImporterHelper stimmeingabeImporterHelper, StimmeingabeImportContext context) {
    var listenkandidatur = stimmeingabe.getListenkandidatur();
    if (listenkandidatur != null) {
      return betrachteteListenkandidaturen.computeIfAbsent(listenkandidatur, x -> {
        @Nullable var listenkandidaturInDb = stimmeingabeImporterListenkandidaturErsetzenStrategie.findeListenkandidatur(wahldurchfuehrungsgebietInDb,
            listenkandidatur.getListenkandidaturTK(), listenkandidatur, listenkandidaturenInDbNachListenkandidaturTk, listenkandidaturenInDbNachNamenUndListeTk,
            gruppeImGebietInDb, gigInDbContext, stimmeingabeImporterHelper, context.getUmgangNichtZugelasseneKandidaten());

        if (listenkandidaturInDb == null) {
          if (context.getUmgangFehlendeKandidaten().isError() && wahldurchfuehrung.isWahlMitListenkandidatenStimmen()) {
            violations.addError("stimmeingabe.import.listenkandidatur.vorhanden", listenkandidatur.getListenkandidaturTK(),
                listenkandidatur.getVorgegebenePosition(), getGruppenposition(listenkandidatur));
          } else if (context.getUmgangFehlendeKandidaten().isError() || context.getUmgangFehlendeKandidaten().isWarn()) {
            violations.addWarning("stimmeingabe.import.listenkandidatur.vorhanden", listenkandidatur.getListenkandidaturTK(),
                listenkandidatur.getVorgegebenePosition(), getGruppenposition(listenkandidatur));
          }
        }
        return listenkandidaturInDb;
      });
    }
    return null;
  }

  @Nullable
  private Long getGruppenposition(Listenkandidatur listenkandidatur) {
    Long gruppenposition = null;
    if (listenkandidatur.getGruppeImGebiet() != null) {
      gruppenposition = listenkandidatur.getGruppeImGebiet().getVorgegebenePosition();
    }
    return gruppenposition;
  }

  private void pruefeReferendumOptionVorhanden(Violations violations, Map<ReferendumOptionTK, ReferendumOption> referendumOptionenInDbNachReferendumOptionTK, Set<ReferendumOption> betrachteteReferendumOptionen, Stimmeingabe stimmeingabe, StimmeingabeImporterReferendumOptionErsetzenStrategie stimmeingabeImporterReferendumOptionErsetzenStrategie, Map<GruppeImGebietTK, GruppeImGebiet> gruppeImGebietInDb, StimmeingabeImporterGigContext gigInDbContext, StimmeingabeImporterHelper stimmeingabeImporterHelper) {
    var referendumOption = stimmeingabe.getReferendumOption();
    if (referendumOption != null) {
      if (!betrachteteReferendumOptionen.contains(referendumOption)) {
        betrachteteReferendumOptionen.add(referendumOption);
        if (!stimmeingabeImporterReferendumOptionErsetzenStrategie.isReferendumOptionVorhanden(referendumOptionenInDbNachReferendumOptionTK, referendumOption,
            gruppeImGebietInDb, gigInDbContext, stimmeingabeImporterHelper)) {
          Long gruppenposition = null;
          if (referendumOption.getGruppeImGebiet() != null) {
            gruppenposition = referendumOption.getGruppeImGebiet().getVorgegebenePosition();
          }
          violations.addError("stimmeingabe.import.referendum.option.vorhanden", referendumOption.getReferendumOptionTK(), referendumOption.getPosition(),
              gruppenposition);
        }
      }
    }
  }

  private void pruefeGebietVorhanden(Violations violations, Map<WahldurchfuehrungsgebietTK, Wahldurchfuehrungsgebiet> gebieteInDB, Set<Wahldurchfuehrungsgebiet> betrachteteGebiete, Ergebniseingang ergebniseingang, StimmeingabeImportContext context) {
    var wahldurchfuehrungsgebietInDatei = ergebniseingang.getWahldurchfuehrungsgebiet();
    if (wahldurchfuehrungsgebietInDatei.getNummerBezirkFallsGesetzt() == null) { //ELECT-11934
      violations.addError("stimmeingabe.import.gebiet.keine.nummer", wahldurchfuehrungsgebietInDatei.getWahldurchfuehrungsgebietTK());
    } else if (betrachteteGebiete.add(wahldurchfuehrungsgebietInDatei)) {
      stimmeingabeImporterHelper.preGebietCheck(gebieteInDB, wahldurchfuehrungsgebietInDatei);
      if (!gebieteInDB.containsKey(wahldurchfuehrungsgebietInDatei.getWahldurchfuehrungsgebietTK())) {
        if (context.getUmgangFehlendeGebiete().isErrorAusgeben()) {
          violations.addError("stimmeingabe.import.gebiet.nicht.vorhanden", wahldurchfuehrungsgebietInDatei.getWahldurchfuehrungsgebietTK(),
              wahldurchfuehrungsgebietInDatei.getNummerBezirkFallsGesetzt());
        }
        if (context.getUmgangFehlendeGebiete().isWarnungAusgeben()) {
          violations.addWarning("stimmeingabe.import.gebiet.nicht.vorhanden", wahldurchfuehrungsgebietInDatei.getWahldurchfuehrungsgebietTK(),
              wahldurchfuehrungsgebietInDatei.getNummerBezirkFallsGesetzt());
        }
      }
    }
  }

  @Override
  protected void erstelleBericht(List<Ergebniseingang> ergebniseingangs, StimmeingabeImportContext context, ImportOptions importOptions, StringBuilder bericht, boolean vorab) {
    var erfasser = ergebniseingangs.stream().map(Ergebniseingang::getErfasser).filter(StringUtils::isNotBlank).distinct().sorted().collect(toList());

    if (!erfasser.isEmpty()) {
      bericht.append(getMessage("stimmeingabe.import.erfasser", String.join(", ", erfasser)));
      bericht.append("\n");
    }
  }

  @Override
  protected StimmeingabeImportExchange getExchange() {
    return stimmeingabeImportExchange;
  }

  @Override
  public Meta getMetaForEntityInternal() {
    return ergebniseingangMeta;
  }

  protected StimmeingabeImporterGruppeImGebietErsetzenStrategie getStimmeingabeImporterGruppeImGebietErsetzenStrategie(StimmeingabeImportContext context) {
    return getStimmeingabeImporterErsetzenStrategie(context).getStimmeingabeImporterGruppeImGebietErsetzenStrategie();
  }

  protected StimmeingabeImporterListenkandidaturErsetzenStrategie getStimmeingabeImporterListenkandidaturErsetzenStrategie(StimmeingabeImportContext context) {
    return getStimmeingabeImporterErsetzenStrategie(context).getStimmeingabeImporterListenkandidaturErsetzenStrategie();
  }

  protected StimmeingabeImporterReferendumOptionErsetzenStrategie getStimmeingabeImporterReferendumOptionErsetzenStrategie(StimmeingabeImportContext context) {
    return getStimmeingabeImporterErsetzenStrategie(context).getStimmeingabeImporterReferendumOptionErsetzenStrategie();
  }

  protected StimmeingabeImporterErsetzenStrategie getStimmeingabeImporterErsetzenStrategie(StimmeingabeImportContext context) {
    return getExchange().getStimmeingabeImporterErsetzenStrategie(
        /* siehe de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeImporter.readImportObject */
        context.get(FILEFORMAT, FileFormat.class), context);
  }

  private Gebiet getPersistentesGebietAusErgebniseingang(Wahldurchfuehrung wahldurchfuehrung, Collection<Ergebniseingang> ergebniseingangs) {
    var ergebniseingang = ergebniseingangs.iterator().next();
    return gebietDAO.findByWahlAndNummerAndGebietsartName(wahldurchfuehrung, ergebniseingang.getGebiet().getNummer(),
        ergebniseingang.getGebiet().getGebietsart().getName());
  }

  private EntityAufStimmzettelDaten getEntityAufStimmzettelDaten() {
    return entityAufStimmzettelDatenDAOFactory.getEntityAufStimmzettelDaten();
  }

  protected Wahldurchfuehrung findWahl(StimmeingabeImportContext context) {
    return wahldurchfuehrungDAO.attach(context.getWahldurchfuehrung());
  }
}
