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

import de.ivu.elect.business.datei.entity.DateiImport;
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.ergebnis.entity.Gewaehlt;
import de.ivu.elect.business.exchange.control.AbstractEntityImporter;
import de.ivu.elect.business.exchange.control.ImportExchange;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.boundary.GruppeDAO;
import de.ivu.elect.business.gruppe.boundary.PolitischeGruppeFactory;
import de.ivu.elect.business.gruppe.control.PolitischeGruppeCreate;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.GruppeTK;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietCache;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietCacheFactory;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietEinzelbewerberValidator;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietGruppenGebieteValidator;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.kandidatur.control.DirektkandidaturCreate;
import de.ivu.elect.business.kandidatur.control.ListenkandidaturCreate;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagImportContext;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagImportExchange;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagImporterZulassungsVerhalten;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.kandidatur.entity.Ersatzkandidatur;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.KandidaturExtendedTK;
import de.ivu.elect.business.kandidatur.entity.KandidaturStatus;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.ListenkandidaturMeta;
import de.ivu.elect.business.person.boundary.PersonenVerknuepfungDAO;
import de.ivu.elect.business.person.entity.PersonStatus;
import de.ivu.elect.business.person.entity.PersonenVerknuepfung;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonendatenPairKey;
import de.ivu.elect.business.person.entity.PersonenregisterAbgleichStatus;
import de.ivu.elect.business.wahl.boundary.WahlDAO;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.boundary.DirektkandidatEdit;
import de.ivu.elect.business.wahlvorschlag.boundary.EinzelkandidatCreate;
import de.ivu.elect.business.wahlvorschlag.boundary.ListeEdit;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagDAO;
import de.ivu.elect.business.wahlvorschlag.control.DirektkandidatCreate;
import de.ivu.elect.business.wahlvorschlag.control.ListeCreate;
import de.ivu.elect.business.wahlvorschlag.control.UnattachedWahlvorschlagByTiefeImGebietsbaumComparator;
import de.ivu.elect.business.wahlvorschlag.control.WahlvorschlagByGebietComparator;
import de.ivu.elect.business.wahlvorschlag.control.WahlvorschlagsdokumentCreate;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagMeta;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagTK;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlagsdokument;
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.wahlvorschlag.WahlvorschlagsdokumentFactory;
import de.ivu.elect.util.CollectionSeparator;

import javax.annotation.Nullable;
import javax.ejb.Stateless;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static de.ivu.elect.business.wahlvorschlag.entity.AbstractWahlvorschlag.extractAllKandidaturen;
import static java.util.stream.Collectors.toSet;

@Stateless
public class WahlvorschlagImporter extends AbstractEntityImporter<Wahlvorschlag, WahlvorschlagTK, WahlvorschlagImportContext, WahlvorschlagDateiImportBericht> {

  @Inject
  protected transient WahlvorschlagImportExchange wahlvorschlagImportExchange;

  @Inject
  protected transient WahlvorschlagMeta wahlvorschlagMeta;

  @Inject
  protected transient GruppeImGebietGruppenGebieteValidator gruppeImGebietGruppenGebieteValidator;

  @Inject
  protected transient GruppeImGebietEinzelbewerberValidator gruppeImGebietEinzelbewerberValidator;

  @Inject
  protected transient KandidaturEdit kandidaturEdit;

  @Inject
  protected transient DirektkandidatEdit direktkandidatEdit;

  @Inject
  protected transient ListeEdit listeEdit;

  @Inject
  protected transient WahlDAO wahlDao;

  @Inject
  protected transient GruppeDAO gruppeDAO;

  @Inject
  protected transient GruppeImGebietCacheFactory gruppeImGebietCacheFactory;

  @Inject
  protected transient ListeCreate listeCreate;

  @Inject
  protected transient DirektkandidatCreate direktkandidatCreate;

  @Inject
  protected transient EinzelkandidatCreate einzelkandidatCreate;

  @Inject
  protected transient ListenkandidaturMeta listenkandidaturMeta;

  @Inject
  protected transient WahlvorschlagDAO wahlvorschlagDAO;

  @Inject
  protected transient KandidaturDAO kandidaturDAO;

  @Inject
  protected transient PersonenVerknuepfungDAO personenVerknuepfungDao;

  @Inject
  protected transient ListenkandidaturCreate listenkkandidaturCreate;

  @Inject
  protected transient DirektkandidaturCreate direktkandidaturCreate;

  @Inject
  protected transient ErgebnisDAO ergebnisDAO;

  @Inject
  protected transient WahlvorschlagsdokumentFactory wahlvorschlagsdokumentFactory;

  @Inject
  protected transient WahlvorschlagsdokumentCreate wahlvorschlagsdokumentCreate;

  @Inject
  protected transient Instance<WahlvorschlagImportFilter> wahlvorschlagFilters;

  @Inject
  private transient PolitischeGruppeFactory politischeGruppeFactory;

  @Inject
  private transient PolitischeGruppeCreate politischeGruppeCreate;

  public WahlvorschlagImporter() {
    super();
  }

  @Override
  protected List<Wahlvorschlag> readImportObject(DateiImport dateiImport, WahlvorschlagImportContext context) throws MappingException {
    List<Wahlvorschlag> result = super.readImportObject(dateiImport, context);

    // Wahlvorschläge filtern, falls eine Datei mit zu vielen Informationen importiert wird
    for (WahlvorschlagImportFilter wahlvorschlagFilter : wahlvorschlagFilters) {
      if (wahlvorschlagFilter.isFilterApplicable(dateiImport, context)) {
        result = wahlvorschlagFilter.filtereWahlvorschlaege(result, context);
      }
    }

    return result;
  }

  @Override
  protected boolean isUnterstuetztAnlegenWennNichtVorhanden() {
    return true;
  }

  @Override
  protected void importiereImportObject(CollectionSeparator<Wahlvorschlag, WahlvorschlagTK> separator, WahlvorschlagImportContext context, ImportOptions importOptions,
                                        WahlvorschlagDateiImportBericht importBericht, StringBuilder bericht) {

    Wahl wahl = attach(context.getWahl());

    // ELECTMUC-983
    if (importOptions.isImportDatenUebernehmenWennNichtBereitsVorhanden()) {
      addUnveraenderteWahlvorschlaegeToImportBericht(separator, importBericht);
    }

    importDerListenkandidaturenBehandeln(separator, importOptions, wahl, importBericht);

    super.importiereImportObject(separator, context, importOptions, importBericht, bericht);

    // Flush aufrufen, damit nachfolgende Finder (hier: beim Persistieren der Verknüpfungen) die frisch angelegten Kandidaturen finden
    kandidaturDAO.flush();


    Set<PersonenVerknuepfung> verknuepfungen = extractVerknuepfungen(extractAllKandidaturen(getPersistierteNichtAktualisierteNichtGeloeschteEntities(separator)));
    persistPersonenVerknuepfungenIfNotInDb(wahl, verknuepfungen, importBericht);
  }

  /**
   * Ermittelt genau die importierten Entitäten -- nicht die aktualisierten -- nicht die gelöschten.
   * <p/>
   * Grund: für das Finden von Dubletten mit Bestandsdaten ist die Dublettensuche die bessere Wahl.
   * <p/>
   * cf. ELECT-3363
   *
   * @param separator mit First == importierte Entitäten und Second = bestehende Datenbank-Entitäten
   */
  private Iterable<Wahlvorschlag> getPersistierteNichtAktualisierteNichtGeloeschteEntities(CollectionSeparator<Wahlvorschlag, WahlvorschlagTK> separator) {
    return separator.getInFirstNotInSecond();
  }

  @Override
  protected boolean isUnterstuetztLoescheBestehendenDatenWennNichtInImportDatenVorhanden() {
    return true;
  }

  @Override
  protected void loescheAusDatenbank(Collection<Wahlvorschlag> entitiesInDb, WahlvorschlagImportContext context, WahlvorschlagDateiImportBericht customDateiImportBerichtCustomData,
                                     StringBuilder bericht) {
    Set<Liste> listen = new HashSet<>();
    Set<Direktkandidat> direktkandidaten = new HashSet<>();

    for (Wahlvorschlag wahlvorschlagInDb : entitiesInDb) {
      if (wahlvorschlagInDb.isListe()) {
        listen.add(wahlvorschlagInDb.castTo(Liste.class).getEntity());
      } else if (wahlvorschlagInDb.isDirektkandidat()) {
        direktkandidaten.add(wahlvorschlagInDb.castTo(Direktkandidat.class).getEntity());
      } else {
        throw new IllegalStateException("Wahlvorschlag [" + wahlvorschlagInDb + "] hat nicht unterstützte Wahlvorschlags-Art!");
      }
    }

    deleteListen(listen);
    deleteDirektkandidaten(direktkandidaten);

    kandidaturDAO.flush(); // notwendig, da in persistiere() ein Finder des DAOs aufgerufen wird
  }

  /**
   * NOTE, dass die Aktualisierung zum Import EINES konsistenten Datenstandes aus MEHREREN Dateien dient; nicht zur nachträglichen Aktualisierung auf einen neueren Datenstand (dazu müssen zuerst alle
   * Wahlvorschläge gelöscht werden).
   */
  @Override
  protected void aktualisiere(Wahlvorschlag entityInDatei, Wahlvorschlag entityInDatenbank, WahlvorschlagImportContext context, WahlvorschlagDateiImportBericht importBericht, StringBuilder bericht) {

    Wahl wahl = attach(context.getWahl());
    Ergebnis ergebnisVorperiode = findOrCreateErgebnisVorperiode(wahl);
    Ergebnis ergebnisEndgueltig = findOrCreateErgebnisEndgueltig(wahl);

    if (entityInDatei.isListe()) {
      aktualisiereListe(entityInDatei.castTo(Liste.class).getEntity(), entityInDatenbank.castTo(Liste.class).getEntity(), wahl, ergebnisVorperiode, ergebnisEndgueltig,
              context.getZulassungsVerhalten(), importBericht);
    } else if (entityInDatei.isDirektkandidat()) {
      aktualisiereDirektkandidat(entityInDatei.castTo(Direktkandidat.class).getEntity(),
              entityInDatenbank.castTo(Direktkandidat.class).getEntity());
    }

  }

  private void aktualisiereDirektkandidat(Direktkandidat zuImportierendenDirektkandidaten, Direktkandidat bestehenderDirektkandidat) {

    Direktkandidatur zuImportierendeDirektkandidatur = zuImportierendenDirektkandidaten.getDirektkandidatur();
    Direktkandidatur bestehendeDirektkandidatur = bestehenderDirektkandidat.getDirektkandidatur();

    zuImportierendeDirektkandidatur.copyAttributesUsingSetterMitAnschrift(bestehendeDirektkandidatur);
  }

  /**
   * NOTE: Bei einer Aktualisierung müssen wir davon ausgehen, dass die bestehendeListe schon Listenkandidaturen hat; diese sind zu behalten!
   */
  private void aktualisiereListe(Liste zuImportierendeListe, Liste bestehendeListe, Wahl wahl, Ergebnis ergebnisVorperiode, Ergebnis ergebnisEndgueltig,
                                 WahlvorschlagImporterZulassungsVerhalten zulassungsVerhalten, WahlvorschlagDateiImportBericht importBericht) {

    Map<KandidaturExtendedTK, Kandidatur> bestehendeKandidaturTkMap = getKandidaturenInDbByExtendedTkMitStimmzettelPosition(bestehendeListe.extractAllKandidaturen());

    copyListeAttributes(zuImportierendeListe, bestehendeListe);

    for (Listenkandidatur zuImportierendeListenkandidatur : zuImportierendeListe.getListenkandidaturen()) {
      Optional<Kandidatur> optionalBestehendeKandidatur = Optional.ofNullable(bestehendeKandidaturTkMap.getOrDefault(zuImportierendeListenkandidatur.getKandidaturExtendedTK(), null));

      if (!optionalBestehendeKandidatur.isPresent()) {
        zuImportierendeListenkandidatur.setListe(bestehendeListe);

        setzeZulassungsverhalten(zuImportierendeListenkandidatur, zulassungsVerhalten);

        persistiereListenkandidatur(zuImportierendeListenkandidatur, wahl, ergebnisVorperiode, ergebnisEndgueltig, importBericht);
      }

      optionalBestehendeKandidatur.ifPresent(zuImportierendeListenkandidatur::copyAttributesUsingSetterMitAnschrift);
      // Immerhin vergleicht der Wahlvorschlagsimport die Kandidaturen nur anhand der Listenposition (cf. KandidaturExtendedTK)
    }

    // NOTE: WahlvorschlagStatus hinterher setzen; er wird bei jedem ListenkandidaturCreate zurückgesetzt ...
    if (!zulassungsVerhalten.isZulassungsstatusImportieren()) {
      bestehendeListe.setWahlvorschlagStatus(zulassungsVerhalten.getListeWahlvorschlagStatus());
    }

  }

  private void copyListeAttributes(Liste zuImportierendeListe, Liste bestehendeListe) {
    Date zeitpunktDerEinreichung = bestehendeListe.getZeitpunktDerEinreichung();
    zuImportierendeListe.copyEntityAttributesUsingSetter(bestehendeListe);
    bestehendeListe.setZeitpunktDerEinreichung(zeitpunktDerEinreichung);
  }

  private void setzeZulassungsverhalten(Listenkandidatur zuImportierendeListenkandidatur, WahlvorschlagImporterZulassungsVerhalten zulassungsVerhalten) {
    if (zulassungsVerhalten.isZulassungsstatusImportieren()) {
      return;
    }
    setzeZulassungsVerhalten(zuImportierendeListenkandidatur, zulassungsVerhalten.getListenkandidaturPersonStatus(), zulassungsVerhalten.getListenkandidaturPersonenregisterAbgleichStatus(),
            zulassungsVerhalten.getListenkandidaturKandidaturStatus());
  }

  private void setzeZulassungsVerhalten(Direktkandidatur direktkandidatur, WahlvorschlagImporterZulassungsVerhalten zulassungsVerhalten) {
    if (zulassungsVerhalten.isZulassungsstatusImportieren()) {
      return;
    }
    setzeZulassungsVerhalten(direktkandidatur, zulassungsVerhalten.getDirektkandidaturPersonStatus(), zulassungsVerhalten.getDirektkandidaturPersonenregisterAbgleichStatus(),
            zulassungsVerhalten.getDirektkandidaturKandidaturStatus());
  }

  private void setzeZulassungsVerhalten(Kandidatur kandidatur, PersonStatus personStatus, PersonenregisterAbgleichStatus personenregisterAbgleichStatus, KandidaturStatus kandidaturStatus) {
    kandidatur.setPersonStatus(personStatus);
    kandidatur.setPersonenregisterAbgleichStatus(personenregisterAbgleichStatus);
    kandidatur.setKandidaturStatus(kandidaturStatus);
    for (Ersatzkandidatur ersatzkandidatur : kandidatur.getErsatzkandidatursByUebergeordneteKandidatur()) {
      setzeZulassungsVerhalten(ersatzkandidatur, personStatus, personenregisterAbgleichStatus, kandidaturStatus);
    }
  }

  @Override
  protected void persistiere(Collection<Wahlvorschlag> neueWahlvorschlaege, WahlvorschlagImportContext context, WahlvorschlagDateiImportBericht importBericht, StringBuilder bericht) {

    Wahl wahl = attach(context.getWahl());

    legeGruppenAnWennNichtVorhanden(wahl, neueWahlvorschlaege, bericht);

    Ergebnis ergebnisVorperiode = findOrCreateErgebnisVorperiode(wahl);
    Ergebnis ergebnisEndgueltig = findOrCreateErgebnisEndgueltig(wahl);

    /*
     * NOTE, dass der Cache nicht nur aus Performancegründen sinnvoll ist, sondern auch notwendig um häufige Flushes zu vermeiden
     */
    GruppeImGebietCache gruppeImGebietCache = gruppeImGebietCacheFactory.createNewGruppeImGebietCompleteHierarchyCache(wahl);

    /*
     * HINT: Wahlvorschläge, die näher am Wurzelgebiet sind, zuerst persistieren -- sonst legt der GruppeImGebietCompleteHierarchyCache die GruppeImGebiet-Hierarchie nicht richtig an.
     */
    List<Wahlvorschlag> persistierteWahlvorschlaege = sortByTiefeImGebietsbaum(neueWahlvorschlaege, gruppeImGebietCache).stream()
            .map(wahlvorschlag -> persistiereWahlvorschlag(wahlvorschlag, wahl, ergebnisVorperiode, ergebnisEndgueltig, gruppeImGebietCache, context.getZulassungsVerhalten(), importBericht, context))
            .collect(Collectors.toList());

    // WilldFly 11 - erst flush dann Dokumente anlegen, sonst MySQLIntegrityConstraintViolationException  Cannot add or update a child row: a foreign key constraint fails
    if (!persistierteWahlvorschlaege.isEmpty()) {
      wahlvorschlagDAO.flush();
      for (Wahlvorschlag wahlvorschlag : persistierteWahlvorschlaege) {
        createAndPersistWahlvorschlagsdokumente(wahlvorschlag);
      }
    }
  }

  // ELECTKOM-13
  protected void legeGruppenAnWennNichtVorhanden(Wahl wahl, Collection<Wahlvorschlag> neueWahlvorschlaege, StringBuilder bericht) {
    Set<GruppeTK> vorhandeneGruppen = gruppeDAO.findByWahl(wahl).stream().map(Gruppe::getGruppeTK).collect(toSet());

    for (Wahlvorschlag wahlvorschlag : neueWahlvorschlaege) {
      if (!vorhandeneGruppen.contains(wahlvorschlag.getGruppe().getGruppeTK())) {
        PolitischeGruppe politischeGruppe = politischeGruppeFactory.create();
        wahlvorschlag.getGruppe().copyEntityAttributesUsingSetter(politischeGruppe);

        politischeGruppe.setWahl(wahl);

        politischeGruppeCreate.save(politischeGruppe);
        wahlvorschlag.getGruppe().setSchluessel(politischeGruppe.getSchluessel()); //wurde ggf. in save automatisch vergeben.
        vorhandeneGruppen.add(politischeGruppe.getGruppeTK());
        gruppeDAO.flush(); // notwendig für Relative Position in politischeGruppeCreate
        bericht.append(getMessage("wahlvorschlag.import.gruppe.angelegt", politischeGruppe.getSchluessel()));
        bericht.append("\n");
      }
    }

  }

  protected List<Wahlvorschlag> sortByTiefeImGebietsbaum(Collection<Wahlvorschlag> wahlvorschlaegeAusDatei, GruppeImGebietCache gruppeImGebietCache) {
    List<Wahlvorschlag> result = new ArrayList<>(wahlvorschlaegeAusDatei);

    result.sort(new UnattachedWahlvorschlagByTiefeImGebietsbaumComparator(gruppeImGebietCache));

    return result;
  }

  protected Wahlvorschlag persistiereWahlvorschlag(Wahlvorschlag wahlvorschlag, Wahl wahl, Ergebnis ergebnisVorperiode, Ergebnis ergebnisEndgueltig, GruppeImGebietCache gruppeImGebietCache,
                                                   WahlvorschlagImporterZulassungsVerhalten zulassungsVerhalten, WahlvorschlagDateiImportBericht importBericht, WahlvorschlagImportContext context) {
    Set<GruppeImGebiet> gruppenImGebietAusDatei = wahlvorschlag.getGruppe().getGruppeImGebietsByGruppe();

    tryToAttachGruppeAndGebiet(gruppeImGebietCache, wahlvorschlag.getGruppeImGebiet());

    if (wahlvorschlag.isListe()) {
      Liste liste = wahlvorschlag.castTo(Liste.class).getEntity();

      persistiereListe(liste, wahl, ergebnisVorperiode, ergebnisEndgueltig, gruppeImGebietCache, zulassungsVerhalten, importBericht);

      // NOTE: WahlvorschlagStatus hinterher setzen; er wird bei jedem ListenkandidaturCreate zurückgesetzt ...
      if (!zulassungsVerhalten.isZulassungsstatusImportieren()) {
        liste.setWahlvorschlagStatus(zulassungsVerhalten.getListeWahlvorschlagStatus());
      }
    } else if (wahlvorschlag.isDirektkandidat()) {
      Direktkandidat direktkandidat = wahlvorschlag.castTo(Direktkandidat.class).getEntity();
      Direktkandidatur direktkandidatur = direktkandidat.getDirektkandidatur();

      persistiereDirektkandidat(direktkandidat, wahl, ergebnisVorperiode, ergebnisEndgueltig, gruppeImGebietCache, importBericht);

      setzeZulassungsVerhalten(direktkandidatur, zulassungsVerhalten);
    } else {
      throw new IllegalStateException("Wahlvorschlag [" + wahlvorschlag + "] hat nicht unterstützte Wahlvorschlags-Art!");
    }

    if (wahlvorschlag.getZeitpunktDerEinreichung() == null) {
      wahlvorschlag.setZeitpunktDerEinreichung(new Date());
    }

    // HINT: Kennwort & Kurzname hinterher setzen, um Änderungen durch Create-Beans mitzubekommen (z.B. Einzelbewerber!)
    if (isKennwortAutomatischUebernehmen(context, wahlvorschlag)) {
      wahlvorschlag.setKennwort(getNameAufGebiet(wahlvorschlag));
    }

    if (isKurznameAutomatischUebernehmen(context, wahlvorschlag)) {
      wahlvorschlag.setKurzname(getKurznameAufGebiet(wahlvorschlag));
    }

    aktualisierePositionen(gruppeImGebietCache, gruppenImGebietAusDatei);
    return wahlvorschlag;
  }

  private void createAndPersistWahlvorschlagsdokumente(Wahlvorschlag wahlvorschlag) {
    Collection<Wahlvorschlagsdokument> wahlvorschlagsdokumente = wahlvorschlagsdokumentFactory.createAllWahlvorschlagsdokumenteForEntity(wahlvorschlag);

    wahlvorschlagsdokumentCreate.save(wahlvorschlagsdokumente);
  }

  private String getKurznameAufGebiet(Wahlvorschlag wahlvorschlag) {
    return attach(wahlvorschlag.getGruppe()).getKurznameAufGebiet(wahlvorschlag.getGebiet());
  }

  private String getNameAufGebiet(Wahlvorschlag wahlvorschlag) {
    return attach(wahlvorschlag.getGruppe()).getNameAufGebiet(wahlvorschlag.getGebiet());
  }

  protected boolean isKennwortAutomatischUebernehmen(WahlvorschlagImportContext context, Wahlvorschlag wahlvorschlag) {
    return !wahlvorschlag.hasKennwort() && context.getKennwortVonGruppeUebernehmenWennNichtGesetzt(); // ELECT-5265
  }

  protected boolean isKurznameAutomatischUebernehmen(WahlvorschlagImportContext context, Wahlvorschlag wahlvorschlag) {
    return !wahlvorschlag.hasKurzname() && context.getKurznameVonGruppeUebernehmenWennNichtGesetzt(); // ELECT-5265
  }

  protected void aktualisierePositionen(GruppeImGebietCache gruppeImGebietCache, Set<GruppeImGebiet> gruppenImGebietAusDatei) {
    gruppeImGebietCache.aktualisierePositionen(gruppenImGebietAusDatei);
  }

  private void tryToAttachGruppeAndGebiet(GruppeImGebietCache gruppeImGebietCache, GruppeImGebiet gruppeImGebiet) {
    gruppeImGebiet.setGebiet(findGebiet(gruppeImGebietCache, gruppeImGebiet.getGebiet()));
    gruppeImGebiet.setGruppe(tryToFindPolitischeGruppe(gruppeImGebietCache, gruppeImGebiet.getGruppe()));
  }

  protected Gebiet findGebiet(GruppeImGebietCache gruppeImGebietCache, Gebiet gebiet) {
    return gruppeImGebietCache.getCachedGebiet(gebiet.getGebietTK());
  }

  protected Gruppe tryToFindPolitischeGruppe(GruppeImGebietCache gruppeImGebietCache, Gruppe gruppe) {
    if (gruppeImGebietCache.isGruppeVorhanden(gruppe.getGruppeTK())) {
      return gruppeImGebietCache.getCachedGruppe(gruppe.getGruppeTK());
    }

    // NOTE, dass das Erstellen der Gruppe später im EinzelkandidatCreate übernommen wird!
    return gruppe;
  }

  protected void persistiereListe(Liste liste, Wahl wahl, Ergebnis ergebnisVorperiode, Ergebnis ergebnisEndgueltig, GruppeImGebietCache gruppeImGebietCache,
                                  WahlvorschlagImporterZulassungsVerhalten zulassungsVerhalten, WahlvorschlagDateiImportBericht importBericht) {
    for (Listenkandidatur listenkandidatur : liste.getListenkandidaturen()) {
      setzeZulassungsverhalten(listenkandidatur, zulassungsVerhalten);
    }

    listeCreate.save(liste.getGruppeImGebiet(), liste, gruppeImGebietCache);

    importBericht.increaseListen(1L);

    for (Listenkandidatur listenkandidatur : liste.getListenkandidaturen()) {
      persistiereListenkandidatur(listenkandidatur, wahl, ergebnisVorperiode, ergebnisEndgueltig, importBericht);
    }
  }

  protected void persistiereDirektkandidat(Direktkandidat direktkandidat, Wahl wahl, Ergebnis ergebnisVorperiode, Ergebnis ergebnisEndgueltig, GruppeImGebietCache gruppeImGebietCache,
                                           WahlvorschlagDateiImportBericht importBericht) {
    if (direktkandidat.isEinzelbewerber()) {
      direktkandidat.getGruppe().setWahl(wahl);

      einzelkandidatCreate.save(direktkandidat, gruppeImGebietCache);
    } else if (isGruppeVorhanden(gruppeImGebietCache, direktkandidat.getGruppeImGebiet())) {
      direktkandidatCreate.save(direktkandidat.getGruppeImGebiet(), direktkandidat, gruppeImGebietCache);
    } else {
      throw new IllegalStateException("Versuch, einen Direktkandidaten für eine unbekannte Gruppe zu persistieren! [direktkandidat = " + direktkandidat + "]");
    }

    persistiereDirektkandidatur(direktkandidat.getDirektkandidatur(), wahl, ergebnisVorperiode, ergebnisEndgueltig, importBericht);
  }

  protected boolean isGruppeVorhanden(GruppeImGebietCache gruppeImGebietCache, GruppeImGebiet gruppeImGebiet) {
    return gruppeImGebietCache.isGruppeVorhanden(gruppeImGebiet.getGruppe().getGruppeTK());
  }


  private void importDerListenkandidaturenBehandeln(CollectionSeparator<Wahlvorschlag, WahlvorschlagTK> separator,
                                                    ImportOptions importOptions, Wahl wahl, WahlvorschlagDateiImportBericht importBericht) {
    if (!importOptions.isLoescheBestehendenDatenWennNichtInImportDatenVorhanden() && !importOptions.isImportDatenUebernehmenWennNichtBereitsVorhanden()) {
      return;
    }

    Ergebnis ergebnisVorperiode = findOrCreateErgebnisVorperiode(wahl);
    Ergebnis ergebnisEndgueltig = findOrCreateErgebnisEndgueltig(wahl);
    for (WahlvorschlagTK wahlvorschlagTK : separator.getInBoth()) {
      if (!separator.getFirstMap().get(wahlvorschlagTK).isListe() || !separator.getSecondMap().get(wahlvorschlagTK).isListe()) {
        continue;
      }
      Liste listeInDatei = separator.getFirstMap().get(wahlvorschlagTK).castTo(Liste.class).getEntity();
      Liste listeInDb = separator.getSecondMap().get(wahlvorschlagTK).castTo(Liste.class).getEntity();

      Map<KandidaturExtendedTK, Kandidatur> bestehendeKandidaturTks = getKandidaturenInDbByExtendedTkMitStimmzettelPosition(listeInDb.extractAllKandidaturen());
      Set<Kandidatur> kandidaturenInDatei = listeInDatei.extractAllKandidaturen();

      if (importOptions.isLoescheBestehendenDatenWennNichtInImportDatenVorhanden()) {
        kandidaturenInDbUndNichtInDateiLoeschen(bestehendeKandidaturTks, kandidaturenInDatei);
      }

      if (importOptions.isImportDatenUebernehmenWennNichtBereitsVorhanden() && !importOptions.isAktualisiereBestehendenDaten()) {
        listenkandidaturenInDateiUndNichtInDbAnlegen(wahl, listeInDb, bestehendeKandidaturTks, kandidaturenInDatei, ergebnisVorperiode, ergebnisEndgueltig, importBericht);
      }
    }
  }

  private void listenkandidaturenInDateiUndNichtInDbAnlegen(Wahl wahl, Liste listeInDb, Map<KandidaturExtendedTK, Kandidatur> bestehendeKandidaturTks, Set<Kandidatur> kandidaturenInDatei, Ergebnis ergebnisVorperiode, Ergebnis ergebnisEndgueltig, WahlvorschlagDateiImportBericht importBericht) {
    kandidaturenInDatei.stream()
            .filter(k -> k.isListenkandidatur() && !bestehendeKandidaturTks.containsKey(k.getKandidaturExtendedTK()))
            .forEach(k -> {
              Listenkandidatur listenkandidatur = k.castTo(Listenkandidatur.class).getEntity();
              listenkandidatur.setListe(listeInDb);
              persistiereListenkandidatur(listenkandidatur, wahl, ergebnisVorperiode, ergebnisEndgueltig, importBericht);
            });
  }

  private void kandidaturenInDbUndNichtInDateiLoeschen(Map<KandidaturExtendedTK, Kandidatur> bestehendeKandidaturTks, Set<Kandidatur> kandidaturenInDatei) {
    bestehendeKandidaturTks.keySet().stream()
            .filter(dbTk -> isDbKandidaturNichtInDatei(dbTk, kandidaturenInDatei))
            .map(bestehendeKandidaturTks::get)
            .forEach(kandidaturEdit::delete);
  }

  private boolean isDbKandidaturNichtInDatei(KandidaturExtendedTK kandidaturInDb, Set<Kandidatur> kandidaturenInDatei) {
    return kandidaturenInDatei.stream().noneMatch(lk -> kandidaturInDb.equals(lk.getKandidaturExtendedTK()));
  }

  private void persistiereDirektkandidatur(Direktkandidatur direktkandidatur, Wahl wahl, Ergebnis ergebnisVorperiode, Ergebnis ergebnisEndgueltig, WahlvorschlagDateiImportBericht importBericht) {
    setzeGewaehltErgebnis(direktkandidatur, ergebnisVorperiode, ergebnisEndgueltig);

    setzeWahl(direktkandidatur.getDirektkandidatur(), wahl);

    direktkandidaturCreate.save(direktkandidatur);

    importBericht.increaseDirektkandidaturen(1L);

    // TODO: Werden Ersatzkandidaturen automatisch persistiert?
    importBericht.increaseErsatzkandidaturen(direktkandidatur.getUntergeordneteErsatzkandidaturen().size());
  }

  private void setzeGewaehltErgebnis(Kandidatur kandidatur, Ergebnis ergebnisVorperiode, Ergebnis ergebnisEndgueltig) {
    for (Gewaehlt gewaehlt : kandidatur.getGewaehlte()) {
      if (Ergebnisart.ENDGUELTIG.equals(gewaehlt.getErgebnis().getErgebnisart())) {
        gewaehlt.setErgebnis(ergebnisEndgueltig);
      } else if (Ergebnisart.VORPERIODE.equals(gewaehlt.getErgebnis().getErgebnisart())) {
        gewaehlt.setErgebnis(ergebnisVorperiode);
      } else {
        throw new IllegalStateException("Unbekannte Ergebnisart: " + gewaehlt.getErgebnis().getErgebnisart());
      }
    }

    if (kandidatur.getZugeordneteErsatzkandidatur() != null) {
      setzeGewaehltErgebnis(kandidatur.getZugeordneteErsatzkandidatur(), ergebnisVorperiode, ergebnisEndgueltig);
    }
  }

  private void setzeWahl(Kandidatur kandidatur, Wahl wahl) {
    kandidatur.setWahl(wahl);

    if (kandidatur.getZugeordneteErsatzkandidatur() != null) {
      setzeWahl(kandidatur.getZugeordneteErsatzkandidatur(), wahl);
    }
  }

  private void persistiereListenkandidatur(Listenkandidatur listenkandidatur, Wahl wahl, Ergebnis ergebnisVorperiode, Ergebnis ergebnisEndgueltig, WahlvorschlagDateiImportBericht importBericht) {
    setzeGewaehltErgebnis(listenkandidatur, ergebnisVorperiode, ergebnisEndgueltig);

    setzeWahl(listenkandidatur, wahl);

    listenkkandidaturCreate.save(listenkandidatur);

    importBericht.increaseListenkandidaturen(1L);

    // TODO: Werden Ersatzkandidaturen automatisch persistiert?
    importBericht.increaseErsatzkandidaturen(listenkandidatur.getUntergeordneteErsatzkandidaturen().size());
  }

  private void persistPersonenVerknuepfungenIfNotInDb(Wahl wahl, Set<PersonenVerknuepfung> verknuepfungen, WahlvorschlagDateiImportBericht importBericht) {
    Map<KandidaturExtendedTK, Kandidatur> kandidaturenInDbPerTK = getKandidaturenInDbByExtendedTkMitStimmzettelPosition(getKandidaturenDerWahl(wahl));

    Map<PersonendatenPairKey, PersonenVerknuepfung> personenVerknuepfungen = PersonenVerknuepfung.mappePersonenVerknuepfungenNachPersonendatenPairKey(personenVerknuepfungDao.findByWahl(wahl));

    for (PersonenVerknuepfung verknuepfung : verknuepfungen) {
      Kandidatur kandidaturVerknuepfung1 = verknuepfung.getPerson1().castTo(Kandidatur.class).getEntity();
      Personendaten person1 = waehlePersistiertePerson(kandidaturVerknuepfung1, kandidaturenInDbPerTK);
      Kandidatur kandidaturVerknuepfung2 = verknuepfung.getPerson2().castTo(Kandidatur.class).getEntity();
      Personendaten person2 = waehlePersistiertePerson(kandidaturVerknuepfung2, kandidaturenInDbPerTK);

      if (person1 == null) {
        fehlerPersonNichtGefunden(kandidaturVerknuepfung1, kandidaturenInDbPerTK);
      }

      if (person2 == null) {
        fehlerPersonNichtGefunden(kandidaturVerknuepfung2, kandidaturenInDbPerTK);
      }

      if (!personenVerknuepfungen.containsKey(new PersonendatenPairKey(person1, person2))) {
        verknuepfung.setPersonen(person1, person2);

        persistPersonenVerknuepfung(verknuepfung);

        personenVerknuepfungen.put(verknuepfung.getPersonendatenPairKey(), verknuepfung);
        importBericht.increaseVerknuepfungen(1L);
      }
    }
  }

  private List<Kandidatur> getKandidaturenDerWahl(Wahl wahl) {
    return kandidaturDAO.findByWahl(wahl)/* ELECTKOM-117 */.stream().filter(k -> k.getWahlvorschlag() != null).collect(Collectors.toList());
  }

  private void fehlerPersonNichtGefunden(Kandidatur kandidatur, Map<KandidaturExtendedTK, Kandidatur> kandidaturenInDbPerTK) {
    throw new RuntimeException("Personenverknüpfung kann nicht persistiert werden, da eine zu verknüpfende Person nicht persistent ist.  ZuVerknüpfen=[" + kandidatur + " -- "
            + kandidatur.getDiscriminator() + " --- " + kandidatur.getKandidaturExtendedTK() + "]" + " - Habe: " + kandidaturenInDbPerTK);
  }

  /**
   * Bei additivem Import, kann es sein, dass eine der beiden verknüpften Kandidaturen bereits in der Datenbank war und deshalb nicht durch den Import persistiert wurde.
   * <p>
   * Gibt NULL zurück, wenn die Person weder vom Import persistiert wurde, noch in der Datenbank vorliegt (oder dort vom Import gelöscht wurde).
   */
  @Nullable
  private Personendaten waehlePersistiertePerson(Kandidatur kandidatur, Map<KandidaturExtendedTK, Kandidatur> kandidaturenAusDbPerTK) {
    if (!kandidatur.isPersistent()) {
      return kandidaturenAusDbPerTK.get(kandidatur.getKandidaturExtendedTK());
    }

    return kandidatur;
  }

  private void persistPersonenVerknuepfung(PersonenVerknuepfung verknuepfung) {
    personenVerknuepfungDao.persist(verknuepfung);
  }

  /**
   * NOTE, dass bestehende Kandidaturen mit der STIMMZETTELPOSITION mit den importierten Kandidaturen verglichen werden (und nicht anhand der relativen Position). Die Annahme ist, dass importierte
   * Positionen stets Stimmzettelpositionen sind.
   */
  private Map<KandidaturExtendedTK, Kandidatur> getKandidaturenInDbByExtendedTkMitStimmzettelPosition(Collection<Kandidatur> kandidaturenInDb) {
    Map<KandidaturExtendedTK, Kandidatur> result = new HashMap<>(kandidaturenInDb.size() * 2);

    for (Kandidatur entity : kandidaturenInDb) {
      result.put(new KandidaturExtendedTK(entity, KandidaturExtendedTK.LISTENKANDIDATUR_VORGEGEBENE_POSITION), entity);
    }

    return result;
  }

  private Ergebnis findOrCreateErgebnisVorperiode(Wahl wahl) {
    return findOrCreateErgebnis(wahl, Ergebnisart.VORPERIODE);
  }

  private Ergebnis findOrCreateErgebnisEndgueltig(Wahl wahl) {
    return findOrCreateErgebnis(wahl, Ergebnisart.ENDGUELTIG);
  }

  private Ergebnis findOrCreateErgebnis(Wahl wahl, Ergebnisart ergebnisart) {
    Ergebnis ergebnis;

    List<Ergebnis> vorperiodenergebnisse = ergebnisDAO.findByWahlAndErgebnisart(wahl, ergebnisart);

    if (vorperiodenergebnisse.isEmpty()) {
      ergebnis = new Ergebnis();
      ergebnis.setWahl(wahl);
      ergebnis.setErgebnisart(ergebnisart);
      ergebnisDAO.persist(ergebnis);
    } else {
      ergebnis = vorperiodenergebnisse.get(0);
    }

    return ergebnis;
  }

  private static Set<PersonenVerknuepfung> extractVerknuepfungen(Collection<Kandidatur> kandidaturen) {
    Set<PersonenVerknuepfung> verknuepfungen = new HashSet<>();

    for (Kandidatur kandidatur : kandidaturen) {
      verknuepfungen.addAll(behalteNurVerknuepfungenInnerhalbDerKandidaturen(kandidatur.getPersonenVerknuepfungsByPerson1(), kandidaturen));
      verknuepfungen.addAll(behalteNurVerknuepfungenInnerhalbDerKandidaturen(kandidatur.getPersonenVerknuepfungsByPerson2(), kandidaturen));
    }

    return verknuepfungen;
  }

  private static Collection<PersonenVerknuepfung> behalteNurVerknuepfungenInnerhalbDerKandidaturen(Collection<PersonenVerknuepfung> verknuepfungen, Collection<Kandidatur> kandidaturen) {
    final Set<KandidaturExtendedTK> kandidaturExtendedTKs = Kandidatur.mappeKandidaturenNachExtendedTK(kandidaturen).keySet();

    return verknuepfungen.stream().filter(input -> {
      Kandidatur kandidatur1 = input.getPerson1().castTo(Kandidatur.class).getEntity();
      Kandidatur kandidatur2 = input.getPerson2().castTo(Kandidatur.class).getEntity();

      return kandidaturExtendedTKs.contains(kandidatur1.getKandidaturExtendedTK()) && kandidaturExtendedTKs.contains(kandidatur2.getKandidaturExtendedTK());
    }).collect(toSet());
  }

  @Override
  protected ImportExchange<List<Wahlvorschlag>, WahlvorschlagImportContext> getExchange() {
    return wahlvorschlagImportExchange;
  }

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

  private void deleteListen(Collection<Liste> listen) {
    listeEdit.delete(listen);
  }

  private void deleteDirektkandidaten(Collection<Direktkandidat> direktkandidaten) {
    direktkandidatEdit.delete(direktkandidaten);
  }

  @Override
  protected List<Wahlvorschlag> getAllEntitiesInDB(WahlvorschlagImportContext context) {
    return wahlvorschlagDAO.findByWahl(context.getWahl());
  }

  @Override
  protected Function<Wahlvorschlag, WahlvorschlagTK> getEntityToEntityTKFunction() {
    return input -> {
      if (input != null) {
        return input.getWahlvorschlagTK();
      }

      return null;
    };
  }

  @Override
  protected void validiere(Violations violations, List<Wahlvorschlag> wahlvorschlaege, WahlvorschlagImportContext context, ImportOptions importOptions) {
    List<GruppeImGebiet> gruppenImGebiet = new ArrayList<>();

    for (Wahlvorschlag wahlvorschlag : wahlvorschlaege) {
      // Deaktiviert, da extrem lahm und wenig bis keinen Nutzen, if (validateEntity(violations, kandidatur, kandidatur.getKandidaturTK())) {
      gruppenImGebiet.add(wahlvorschlag.getGruppeImGebiet());
      // }

      for (Kandidatur kandidatur : wahlvorschlag.extractAllKandidaturen()) {
        pruefePersonenVerknuepfung(kandidatur, violations);
      }
    }

    pruefeGruppenImGebiet(context.getWahl(), gruppenImGebiet, violations);

    // ELECT-4052
    for (Wahlvorschlag wahlvorschlag : wahlvorschlaege) {
      validateEntity(violations, wahlvorschlag, wahlvorschlag.getUk(), wahlvorschlagMeta);
      if (wahlvorschlag.isListe()) {
        Liste liste = wahlvorschlag.castTo(Liste.class).getEntity();
        for (Listenkandidatur listenkandidatur : liste.getListenkandidaturen()) {
          validateEntity(violations, listenkandidatur, listenkandidatur.getTk(), listenkandidaturMeta);
        }
      }
    }
  }

  private void pruefePersonenVerknuepfung(Kandidatur kandidatur, Violations violations) {
    // NOTE, dass Person1 nicht null sein kann, da die Verknüpfung immer für diese angelegt wird!
    if (kandidatur.getPersonenVerknuepfungsByPerson1().stream().anyMatch(verknuepfung -> verknuepfung.getPerson2() == null)) {
      violations.addError("wahlvorschlag.import.verknuepfung.inkonsistent", kandidatur.getNamePraesentation());
    }
  }

  protected void pruefeGruppenImGebiet(Wahl wahl, List<GruppeImGebiet> gruppenImGebiet, Violations violations) {
    gruppeImGebietGruppenGebieteValidator.pruefeExistenzAlleGruppenUndGebiete(gruppenImGebiet, wahl, violations);
    gruppeImGebietEinzelbewerberValidator.pruefeEinzelbewerberAufKeinemAnderemWahlkreisInDB(gruppenImGebiet, wahl, violations);
  }

  @Override
  protected void postImportActions(List<Wahlvorschlag> wahlvorschlaege, WahlvorschlagImportContext context, ImportOptions importOptions, WahlvorschlagDateiImportBericht importBericht,
                                   StringBuilder bericht) {
    super.postImportActions(wahlvorschlaege, context, importOptions, importBericht, bericht);

    boolean vorab = false;
    erstelleBericht(importBericht, bericht, vorab);
  }

  @Override
  protected void erstelleBericht(List<Wahlvorschlag> importObject, WahlvorschlagImportContext context, ImportOptions importOptions, StringBuilder berichtBuilder, boolean vorab) {
    if (vorab) {
      // TODO: Genau genommen müssten wir erst in die Datenbank schauen, was es dort schon gibt, und ob das aktualisiert wird...
      WahlvorschlagDateiImportBericht importBericht = new WahlvorschlagDateiImportBericht();

      for (Wahlvorschlag wahlvorschlag : importObject) {
        if (wahlvorschlag.isListe()) {
          importBericht.increaseListen(1L);
        }

        for (Kandidatur kandidatur : wahlvorschlag.extractAllKandidaturen()) {
          if (kandidatur.isDirektkandidatur()) {
            importBericht.increaseDirektkandidaturen(1L);
          }
          if (kandidatur.isListenkandidatur()) {
            importBericht.increaseListenkandidaturen(1L);
          }
          if (kandidatur.isErsatzkandidatur()) {
            importBericht.increaseErsatzkandidaturen(1L);
          }

          if (!kandidatur.getPersonenVerknuepfungsByPerson1().isEmpty() || !kandidatur.getPersonenVerknuepfungsByPerson2().isEmpty()) {
            importBericht.increaseVerknuepfungen(1L);
          }
        }
      }

      addUnveraenderteWahlvorschlaegeToImportBericht(importObject, context, importBericht);

      erstelleBericht(importBericht, berichtBuilder, vorab);
    }
  }

  private void erstelleBericht(WahlvorschlagDateiImportBericht importBericht, StringBuilder berichtBuilder, boolean vorab) {
    List<String> ignorierteWahlvorschlaege = importBericht.getIgnorierteWahlvorschlaegeMessages();
    if (ignorierteWahlvorschlaege.size() > 0) {
      berichtBuilder.append(ignorierteWahlvorschlaege.stream().map(wahlvorschlagMessage -> wahlvorschlagMessage + "\n").collect(Collectors.joining("", "", "\n")));
    }

    if (vorab) {
      // HINT: Benutzer informieren, dass dies nicht die tatsächlich importierte Anzahl an Entitäten ist
      berichtBuilder.append(getMessage("wahlvorschlag.import.anzahl.entitaeten.in.datei")).append('\n');
    }

    berichtBuilder.append(getMessage("wahlvorschlag.import.anzahl.direktkandidaturen", importBericht.getImportierteDirektkandidaturen())).append('\n');
    berichtBuilder.append(getMessage("wahlvorschlag.import.anzahl.listen", importBericht.getImportierteListen())).append('\n');
    berichtBuilder.append(getMessage("wahlvorschlag.import.anzahl.listenkandidaturen", importBericht.getImportierteListenkandidaturen())).append('\n');
    berichtBuilder.append(getMessage("wahlvorschlag.import.anzahl.ersatzkandidaturen", importBericht.getImportierteErsatzkandidaturen())).append('\n');
    berichtBuilder.append(getMessage("wahlvorschlag.import.anzahl.doppelkandidaturen", importBericht.getImportierteVerknuepfungen() / 2));
  }

  protected void addUnveraenderteWahlvorschlaegeToImportBericht(List<Wahlvorschlag> entitiesInDatei, WahlvorschlagImportContext context, WahlvorschlagDateiImportBericht importBericht) {
    addUnveraenderteWahlvorschlaegeToImportBericht(getSeparator(entitiesInDatei, context), importBericht);
  }

  /**
   * @param separator mit First == importierte Entitäten und Second = bestehende Datenbank-Entitäten
   */
  protected void addUnveraenderteWahlvorschlaegeToImportBericht(CollectionSeparator<Wahlvorschlag, WahlvorschlagTK> separator,
                                                                WahlvorschlagDateiImportBericht importBericht) {
    Map<WahlvorschlagTK, Wahlvorschlag> wahlvorschlagMap = separator.getSecondMap();

    separator.getInBoth().stream()
            .map(wahlvorschlagMap::get)
            .filter(Objects::nonNull)
            .sorted(new WahlvorschlagByGebietComparator())
            .forEach(wahlvorschlag -> {
              Gebiet wahlvorschlagGebiet = wahlvorschlag.getGebiet();
              importBericht.addUnveraenderterWahlvorschlag(
                      getMessage("wahlvorschlag.import.wahlvorschlag.unveraendert", wahlvorschlag.getNameAufGebiet(),
                              wahlvorschlagGebiet.getGebietsart().lokalisiere(getMessages()), wahlvorschlagGebiet.getNummer()));
            });

  }

  protected Wahl attach(Wahl wahl) {
    return wahlDao.attach(wahl);
  }

  protected Gruppe attach(PolitischeGruppe gruppe) {
    return gruppeDAO.attach(gruppe);
  }

}
