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

import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.function.Function.identity;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

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.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.WusFeaturesAktiviert;
import de.ivu.elect.business.benutzerverwaltung.entity.Rolle;
import de.ivu.elect.business.datei.entity.DateiImport;
import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.dokumente.boundary.DokumentVorlageImporter;
import de.ivu.elect.business.dokumente.control.DefaultDokumentVorlagenInitEvent;
import de.ivu.elect.business.dokumente.control.DokumentVorlageExchangeContext;
import de.ivu.elect.business.dokumente.entity.DokumentVorlage;
import de.ivu.elect.business.ergebnis.boundary.ErgebnisDAO;
import de.ivu.elect.business.ergebnis.boundary.GewaehltDAO;
import de.ivu.elect.business.ergebnis.boundary.SitzeDAO;
import de.ivu.elect.business.ergebnis.boundary.StimmenDAO;
import de.ivu.elect.business.ergebnis.entity.AbstractErgebnis;
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.ergebnis.entity.Sitze;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.exchange.control.AbstractImporter;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.exchange.entity.ImportUpdateReport;
import de.ivu.elect.business.fremdkandidatur.control.FremdkandidaturCreate;
import de.ivu.elect.business.fremdkandidatur.entity.Fremdkandidatur;
import de.ivu.elect.business.gebietsbaum.boundary.GebietDAO;
import de.ivu.elect.business.gebietsbaum.boundary.GebietPolygonPointDAO;
import de.ivu.elect.business.gebietsbaum.boundary.GebietsartDAO;
import de.ivu.elect.business.gebietsbaum.boundary.GebietsarteigenschaftDAO;
import de.ivu.elect.business.gebietsbaum.boundary.GebietsbaumImporter;
import de.ivu.elect.business.gebietsbaum.boundary.GebietsgruppierungDAO;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.control.GebietCreate;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPolygonPoint;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gebietsbaum.entity.GebietsartUK;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsarteigenschaft;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsgruppierung;
import de.ivu.elect.business.gruppe.boundary.GruppeDAO;
import de.ivu.elect.business.gruppe.entity.AbstractGruppe;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietCreate;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.image.boundary.BildDAO;
import de.ivu.elect.business.internet.boundary.InternetKonfigurationImporter;
import de.ivu.elect.business.internet.entity.InternetSeite;
import de.ivu.elect.business.kandidatenportal.control.WvpWarnungCreate;
import de.ivu.elect.business.kandidatenportal.entity.WvpWarnung;
import de.ivu.elect.business.kandidatur.control.KandidaturCreate;
import de.ivu.elect.business.kandidatur.entity.Ersatzkandidatur;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.mandant.entity.MandantUK;
import de.ivu.elect.business.person.boundary.PersonenVerknuepfungDAO;
import de.ivu.elect.business.person.entity.PersonenVerknuepfung;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.staat.boundary.StaatDAO;
import de.ivu.elect.business.staat.control.StaatenGruppeCreate;
import de.ivu.elect.business.staat.entity.Staat;
import de.ivu.elect.business.staat.entity.StaatenGruppe;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabePlausiKonfigurationDAO;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabePlausiKonfigurationCreate;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiKonfiguration;
import de.ivu.elect.business.stimmzettel.boundary.StimmzettelDAO;
import de.ivu.elect.business.stimmzettel.boundary.StimmzettelKopfdatumDAO;
import de.ivu.elect.business.stimmzettel.entity.Stimmzettel;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatum;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.system.entity.SystemLogQuelle;
import de.ivu.elect.business.veranstaltung.boundary.VeranstaltungImporter;
import de.ivu.elect.business.veranstaltung.control.VeranstaltungExchangeContext;
import de.ivu.elect.business.wahl.control.WahlCreate;
import de.ivu.elect.business.wahl.control.WahlImportObject;
import de.ivu.elect.business.wahl.control.Wahldaten;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlCreateDokumenteUebernehmen;
import de.ivu.elect.business.wahl.entity.WahlImportContext;
import de.ivu.elect.business.wahl.entity.WahlKonfiguration;
import de.ivu.elect.business.wahl.entity.WahlKonfigurationExchangeContext;
import de.ivu.elect.business.wahl.entity.WahlMeta;
import de.ivu.elect.business.wahl.entity.WahlParameter;
import de.ivu.elect.business.wahl.entity.WahlParameterLoader;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.business.wahl.entity.WahlUK;
import de.ivu.elect.business.wahl.entity.Wahlart;
import de.ivu.elect.business.wahl.entity.WahlartUK;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.business.wahl.entity.WahlterminUK;
import de.ivu.elect.business.wahlausschuss.boundary.WahlausschussEntschaedigungDAO;
import de.ivu.elect.business.wahlausschuss.control.WahlausschussmitgliedCreate;
import de.ivu.elect.business.wahlausschuss.entity.WahlausschussEntschaedigung;
import de.ivu.elect.business.wahlausschuss.entity.WahlausschussEntschaedigungTK;
import de.ivu.elect.business.wahlausschuss.entity.Wahlausschussmitglied;
import de.ivu.elect.business.wahlbeteiligung.boundary.WahlbeteiligungVerlaufDAO;
import de.ivu.elect.business.wahlbeteiligung.control.WahlbeteiligungVerlaufCreate;
import de.ivu.elect.business.wahlbeteiligung.entity.WahlbeteiligungVerlauf;
import de.ivu.elect.business.wahlbeteiligung.entity.WahlbeteiligungVerlaufTK;
import de.ivu.elect.business.wahlrechtspruefung.boundary.PruefkriteriumDAO;
import de.ivu.elect.business.wahlrechtspruefung.control.PruefkriteriumCreate;
import de.ivu.elect.business.wahlrechtspruefung.control.WahlrechtspruefungCreate;
import de.ivu.elect.business.wahlrechtspruefung.entity.Pruefkriterium;
import de.ivu.elect.business.wahlrechtspruefung.entity.PruefkriteriumTK;
import de.ivu.elect.business.wahlrechtspruefung.entity.Wahlrechtspruefung;
import de.ivu.elect.business.wahlvorschlag.control.WahlvorschlagCreate;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlagsperson.control.WahlvorschlagsPersonCreate;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPerson;
import de.ivu.elect.jee.entity.Meta;
import de.ivu.elect.jee.entity.customdata.EmptyCustomData;
import de.ivu.elect.jee.exchange.ImportOptions;
import de.ivu.elect.jee.exchange.Violations;

public abstract class AbstractWahlImporter extends AbstractImporter<WahlImportObject, WahlImportContext, DateiImportBerichtCustomData> {

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

  @Inject
  protected transient WahlMeta wahlMeta;

  @Inject
  protected transient WahlDAO wahlDAO;

  @Inject
  protected transient WahlCreate wahlCreate;

  @Inject
  protected transient GebietCreate gebietCreate;

  @Inject
  protected transient GebietsartDAO gebietsartDAO;

  @Inject
  protected transient GruppeDAO gruppeDAO;

  @Inject
  protected transient GebietDAO gebietDAO;

  @Inject
  protected transient GruppeImGebietCreate gruppeImGebietCreate;

  @Inject
  protected transient StaatDAO staatDAO;

  @Inject
  protected transient WahlartDAO wahlartDAO;

  @Inject
  protected transient PersonenVerknuepfungDAO personenVerknuepfungDAO;

  @Inject
  protected transient ErgebnisDAO ergebnisDAO;

  @Inject
  protected transient StimmenDAO stimmenDAO;

  @Inject
  protected transient GewaehltDAO gewaehltDAO;

  @Inject
  protected transient SitzeDAO sitzeDAO;

  @Inject
  protected transient KandidaturCreate kandidaturCreate;

  @Inject
  protected transient FremdkandidaturCreate fremdkandidaturCreate;

  @Inject
  protected transient WahlvorschlagCreate wahlvorschlagCreate;

  @Inject
  protected transient WahlvorschlagsPersonCreate wahlvorschlagsPersonCreate;

  @Inject
  protected transient WahlausschussmitgliedCreate wahlausschussmitgliedCreate;

  @Inject
  protected transient WahlausschussEntschaedigungDAO wahlausschussEntschaedigungDAO;

  @Inject
  protected transient StaatenGruppeCreate staatenGruppeCreate;

  @Inject
  protected transient GebietsgruppierungDAO gebietsgruppierungDAO;

  @Inject
  protected transient GebietsbaumImporter gebietsbaumImporter;

  @Inject
  protected transient WahlKonfigurationImporter wahlKonfigurationImporter;

  @Inject
  protected transient WahlkonfigurationAnpassbar wahlkonfigurationAnpassbar;

  @Inject
  protected transient GebietPolygonPointDAO gebietPolygonPointDAO;

  @Inject
  protected transient GebietsarteigenschaftDAO gebietsarteigenschaftDAO;

  @Inject
  protected transient BildDAO bildDAO;

  @Inject
  protected transient WusFeaturesAktiviert wusFeaturesAktiviert;

  @Inject
  protected transient InternetKonfigurationImporter internetKonfigurationImporter;

  @Inject
  protected transient WahlbeteiligungVerlaufCreate wahlbeteiligungVerlaufCreate;

  @Inject
  protected transient WahlbeteiligungVerlaufDAO wahlbeteiligungVerlaufDAO;

  @Inject
  protected transient StimmeingabePlausiKonfigurationCreate stimmeingabePlausiKonfigurationCreate;

  @Inject
  protected transient StimmeingabePlausiKonfigurationDAO stimmeingabePlausiKonfigurationDAO;

  @Inject
  protected transient DokumentVorlageImporter dokumentVorlageImporter;

  @Inject
  protected transient WahlParameterLoader wahlParameterLoader;

  @Inject
  protected transient VeranstaltungImporter veranstaltungImporter;

  @Inject
  protected transient SystemLogWriter systemLogWriter;

  @Inject
  protected transient StimmzettelDAO stimmzettelDAO;

  @Inject
  protected transient StimmzettelKopfdatumDAO stimmzettelKopfdatumDAO;

  @Inject
  protected transient PruefkriteriumDAO pruefkriteriumDAO;

  @Inject
  protected transient PruefkriteriumCreate pruefkriteriumCreate;

  @Inject
  protected transient WahlrechtspruefungCreate wahlrechtspruefungCreate;

  @Inject
  protected transient WvpWarnungCreate wvpWarnungCreate;

  @Inject
  protected transient DefaultDokumentVorlagenInitEvent defaultDokumentVorlagenInitEvent;

  public AbstractWahlImporter() {
    super();
  }

  @Override
  protected void validiere(Violations violations, WahlImportObject wahlImportObject, WahlImportContext context, ImportOptions importOptions) {
    Wahldaten<Wahl, Gebiet> wahldaten = wahlImportObject.getWahldaten();

    validiereWahl(context, wahldaten, violations);
    validiereGebiete(wahldaten, violations, context);

    if (!wahlkonfigurationAnpassbar.isWahlkonfigurationAnpassbar(context.getMandant()) || !wahlImportObject.isWahlkonfigurationVorhanden()) {
      validiereWahlartGebietsartenVorhanden(violations, wahlImportObject, context);
    }

    validiereDokumentVorlagen(wahldaten, wahlImportObject.getDokumentVorlagen(), violations);

    validateExistingStaaten(wahldaten, violations, staatDAO.findByAllOrderedByNameStaatsangehoerigkeit());

    validiereVeranstaltungen(wahldaten, violations);
  }

  private void validiereWahlartGebietsartenVorhanden(Violations violations, WahlImportObject wahlImportObject, WahlImportContext context) {
    if (wahlImportObject.getWahldaten().getWahl().getWahlart() == null || isBlank(wahlImportObject.getWahldaten().getWahl().getWahlart().getName())) {
      return; // wird vorher schon validiert
    }

    Wahlart wahlartIndDb = bestimmeWahlart(wahlImportObject.getWahldaten(), context);

    if (wahlartIndDb == null) {
      violations.addError("wahl.import.violation.error.wahlart.nicht.vorhanden", wahlImportObject.getWahldaten().getWahl().getWahlart().getName());

      return;
    }

    Set<String> betrachteteGebietsarten = new HashSet<>();

    for (Gebiet gebiet : filterZuImportierendeGebiete(wahlImportObject.getWahldaten(), wahlartIndDb)) {
      if (gebiet.getGebietsart() != null && isNotBlank(gebiet.getGebietsart().getName())) {
        String gebietsartName = gebiet.getGebietsart().getName();

        if (!betrachteteGebietsarten.contains(gebietsartName)) {
          betrachteteGebietsarten.add(gebietsartName);

          List<Gebietsart> gebietsartenInDB = gebietsartDAO.findByName(context.getMandant(), gebietsartName);

          if (gebietsartenInDB.size() == 1) {
            Gebietsart gebietsartInDb = gebietsartenInDB.get(0);

            try {
              gebietsarteigenschaftDAO.findByWahlartAndGebietsart(wahlartIndDb, gebietsartInDb);
            } catch (NoResultException nre) {
              violations.addError("wahl.import.violation.error.gebietsart.nicht.wahlart.zugeordnet", wahlartIndDb.getTitel(), wahlartIndDb.getName(), gebietsartInDb.getTitel(),
                  gebietsartInDb.getName());
            }
          } else {
            violations.addError("wahl.import.violation.error.gebietsart.nicht.vorhanden", gebietsartName);
          }
        }
      }
    }
  }

  @Nullable
  private Wahlart bestimmeWahlart(Wahldaten<Wahl, Gebiet> wahldaten, WahlImportContext context) {
    return findeWahlartInDb(wahldaten.getWahl().getWahlart(), context);
  }

  protected List<Gebiet> filterZuImportierendeGebiete(Wahldaten<Wahl, Gebiet> wahldaten, Wahlart wahlartInDb) {
    // BEIM ÜBERSCHREIBEN, isPersistiereGruppeImGebiet beachten(!)
    return wahldaten.getGebiete(); // nichts zu tun
  }

  @Nullable
  protected Wahlart findeWahlartInDb(Wahlart wahlart, WahlImportContext context) {
    try {
      return wahlartDAO.findByName(context.getMandant(), wahlart.getName());
    } catch (NoResultException nre) {
      return null;
    }
  }

  protected void validiereWahl(WahlImportContext context, Wahldaten<Wahl, Gebiet> wahldaten, Violations violations) {
    boolean abbrechen = false;
    if (wahldaten.getWahl().getWahldatum() == null) {
      violations.addError("wahl.import.violation.error.wahldatum.fehlt");
      abbrechen = true;
    }
    if (wahldaten.getWahl().getWahlart() == null || isBlank(wahldaten.getWahl().getWahlart().getName())) {
      violations.addError("wahl.import.violation.error.wahlart.fehlt");
      abbrechen = true;
    }
    if (abbrechen) {
      return;
    }

    boolean ok;

    try {
      // UK manuell bauen, Mandant muss gesetzt werden
      MandantUK mandantUk = context.getMandant().getMandantUK();
      WahlartUK wahlartUk = new WahlartUK(wahldaten.getWahl().getWahlart().getName(), mandantUk);
      WahlterminUK wahlterminUk = new WahlterminUK(wahldaten.getWahl().getWahlterminNummer(), wahldaten.getWahl().getWahldatum(), mandantUk);
      WahlUK wahlUK = new WahlUK(wahlartUk, wahlterminUk);

      Wahl wahl = wahlDAO.findByUK(wahlUK);
      ok = wahl == null; // es darf keine Wahl vorhanden sein
    } catch (NoResultException e) {
      ok = true;
    }

    if (!ok) {
      violations.addError("wahl.import.violation.error.schon.vorhanden");
    }

  }

  protected void validiereGebiete(Wahldaten<Wahl, Gebiet> wahldaten, Violations violations, WahlImportContext context) {
    if (wahldaten.getWahl().getWahlart() != null) { // nur prüfen, wenn Wahlart klar ist
      if (wahldaten.getWurzelgebiet() != null && (wahldaten.getWurzelgebiet().getGebietsart() == null || isBlank(wahldaten.getWurzelgebiet().getGebietsart().getName()))) {
        violations.addError("wahl.import.violation.error.gebietsart.wurzelgebiet.fehlt");
      }
      List<Gebiet> gebiete = filterZuImportierendeGebiete(wahldaten, bestimmeWahlart(wahldaten, context));
      if (gebiete.stream().anyMatch(gebiet -> gebiet.getGebietsart() == null || isBlank(gebiet.getGebietsart().getName()))) {
        violations.addError("wahl.import.violation.error.gebietsart.fehlt");
      }

      Map<Long, Mandant> mandantenInDb = gebietsbaumImporter.sucheMandanten();
      Set<Gebiet> wurzelgebiete = new HashSet<>();
      for (Gebiet gebiet : gebiete) {
        if (!gebiet.hasUebergeordnetesGebiet()) {
          wurzelgebiete.add(gebiet);
        }
        gebietsbaumImporter.validateGebietMandant(violations, mandantenInDb, gebiet);
      }
      if (wurzelgebiete.size() > 1) {
        violations.addError("wahl.import.violation.error.gebietsart.zu.viele.wurzelgebiete", wurzelgebiete);
      }
    }
  }

  private void validateExistingStaaten(Wahldaten<Wahl, Gebiet> wahldaten, Violations violations, List<Staat> staatenInDb) {
    validiereKandidaturStaatsangehoerigkeiten(wahldaten.getKandidaturen(), violations, staatenInDb);
    validiereWahlausschussStaatsangehoerigkeiten(wahldaten.getWahlausschussmitglieder(), violations, staatenInDb); // ELECTMUC-1317
    validiereStaatenGruppeStaaten(wahldaten.getStaatenGruppen(), violations, staatenInDb);
  }

  protected void validiereKandidaturStaatsangehoerigkeiten(List<Kandidatur> kandidaturen, Violations violations, List<Staat> staatenInDb) {
    for (Kandidatur kandidatur : kandidaturen) {
      validateExistingStaaten(kandidatur.getStaatsangehoerigkeitenSorted(), violations, "wahl.import.violation.error.kandidatur.staatsangehoerigkeit.nicht.vorhanden", staatenInDb);
    }
  }

  protected void validiereWahlausschussStaatsangehoerigkeiten(List<Wahlausschussmitglied> wahlausschussmitglieder, Violations violations, List<Staat> staatenInDb) {
    for (Wahlausschussmitglied wahlausschussmitglied : wahlausschussmitglieder) {
      validateExistingStaaten(wahlausschussmitglied.getStaatsangehoerigkeitenSorted(), violations, "wahl.import.violation.error.wahlauschussmitglied.staatsangehoerigkeit.nicht.vorhanden",
          staatenInDb);
    }
  }

  private void validiereStaatenGruppeStaaten(List<StaatenGruppe> staatenGruppen, Violations violations, List<Staat> staatenInDb) {
    for (StaatenGruppe staatenGruppe : staatenGruppen) {
      validateExistingStaaten(staatenGruppe.getStaats(), violations, "wahl.import.violation.error.staaten.gruppe.staat.nicht.vorhanden", staatenInDb);
    }
  }

  private void validateExistingStaaten(Collection<Staat> staaten, Violations violations, String violationMsgKey, List<Staat> staatenInDb) {
    for (Staat staat : staaten) {
      String iso3166Alpha2 = staat.getIso3166Alpha2();
      String customCodeStaat = staat.getCustomCodeStaat();
      String customCodeStaatsangehoerigkeit = staat.getCustomCodeStaatsangehoerigkeit();

      if (!matchesExistingStaat(iso3166Alpha2, customCodeStaat, customCodeStaatsangehoerigkeit, staatenInDb)) {
        violations.addError(violationMsgKey, iso3166Alpha2, customCodeStaat, customCodeStaatsangehoerigkeit);
      }
    }
  }

  private boolean matchesExistingStaat(@Nullable String iso3166Alpha2, @Nullable String customCodeStaat, @Nullable String customCodeStaatsangehoerigkeit, List<Staat> staaten) {
    return getExistingStaat(iso3166Alpha2, customCodeStaat, customCodeStaatsangehoerigkeit, staaten) != null;
  }

  @Override
  protected void erstelleBericht(WahlImportObject wahlImportObject, WahlImportContext noContext, ImportOptions importOptions, StringBuilder bericht, boolean vorab) {
    Wahldaten<Wahl, Gebiet> wahldaten = wahlImportObject.getWahldaten();
    Wahl wahl = wahldaten.getWahl();

    bericht.append("Wahldatum: ");

    if (wahl.getWahldatum() != null) {
      bericht.append(getUserLocale().formatDate(wahl.getWahldatum()));
    }

    bericht.append('\n');
    bericht.append("Name: ");
    if (wahl.getName() != null) {
      bericht.append(wahl.getName());
    }

    bericht.append('\n');
    bericht.append("Anzahl Gebiete: ");
    bericht.append(wahldaten.getGebiete().size());
    bericht.append('\n');
    bericht.append("Anzahl Gruppen: ");
    bericht.append(wahldaten.getGruppen().size());
    bericht.append('\n');
    bericht.append("Anzahl Gruppen im Gebiet: ");
    bericht.append(wahldaten.getGruppenImGebiet().size());
    bericht.append('\n');
    bericht.append("Anzahl Wahlvorschläge: ");
    bericht.append(wahldaten.getWahlvorschlaege().size());
    bericht.append('\n');
    bericht.append("Anzahl Kandidaturen: ");
    bericht.append(wahldaten.getKandidaturen().size());
    bericht.append('\n');
    bericht.append("Anzahl Verknüpfungen: ");
    bericht.append(wahldaten.getPersonenVerknuepfungen().size());
    bericht.append('\n');
    bericht.append("Anzahl Wahlrechtsprüfkriterien: ");
    bericht.append(wahldaten.getPruefkriterien().size());
    bericht.append('\n');
    bericht.append("Anzahl Wahlrechtsprüfungen: ");
    bericht.append(wahldaten.getWahlrechtspruefungen().size());
    bericht.append('\n');
    bericht.append("Anzahl WVP-Warnungen: ");
    bericht.append(wahldaten.getWvpWarnungen().size());
    bericht.append('\n');
    bericht.append("Anzahl Stimmen der Vorperiode: ");
    bericht.append(wahldaten.getStimmenVorperiode().size());
    bericht.append('\n');
    bericht.append("Anzahl Gewählte der Vorperiode: ");
    bericht.append(wahldaten.getGewaehltVorperiode().size());
    bericht.append('\n');
    bericht.append("Anzahl Sitzergebnisse der Vorperiode: ");
    bericht.append(wahldaten.getSitzeVorperiode().size());
    bericht.append('\n');
    bericht.append("Anzahl Referendumoptionen: ");
    bericht.append(wahldaten.getReferendumOptionen().size());
    bericht.append('\n');
    bericht.append("Anzahl Wahlvorschlagspersonen: ");
    bericht.append(wahldaten.getWahlvorschlagsPersonen().size());
    bericht.append('\n');
    bericht.append("Anzahl Wahlausschussmitglieder: ");
    bericht.append(wahldaten.getWahlausschussmitglieder().size());
    bericht.append('\n');
    bericht.append("Anzahl Staatengruppen: ");
    bericht.append(wahldaten.getStaatenGruppen().size());
    bericht.append('\n');
    bericht.append("Anzahl Gebietsgruppierungen: ");
    bericht.append(wahldaten.getGebietsgruppierungen().size());
    bericht.append('\n');
    bericht.append("Anzahl Gebietpolygone: ");
    bericht.append(wahldaten.getGebietPolygonPoints().size());
    bericht.append('\n');
    bericht.append("Anzahl Internetseiten: ");
    bericht.append(wahldaten.getInternetSeiten().size());
    bericht.append('\n');
    bericht.append("Anzahl Plausi-Konfigurationen: ");
    bericht.append(wahldaten.getStimmeingabePlausiKonfigurationen().size());
    bericht.append('\n');
    bericht.append("Anzahl vorläufiger Stimmen: ");
    bericht.append(wahldaten.getStimmen(Ergebnisart.VORLAEUFIG).size());
    bericht.append('\n');
    bericht.append("Anzahl endgültiger Stimmen: ");
    bericht.append(wahldaten.getStimmen(Ergebnisart.ENDGUELTIG).size());
    bericht.append('\n');
    bericht.append("Anzahl Veranstaltungen: ");
    bericht.append(wahldaten.getVeranstaltungen().size());
    bericht.append('\n');
  }

  @Override
  protected ImportUpdateReport createImportUpdateReport(WahlImportObject wahlImportObject, Violations violations, WahlImportContext context, ImportOptions importOptions, StringBuilder bericht) {
    return new ImportUpdateReport(violations, 1, wahlDAO.findByAll(context.getMandant()).size(), 0, importOptions, bericht);
  }

  @Override
  protected void anlegenWennNichtVorhanden(WahlImportObject wahlImportObject, WahlImportContext wahlImportContext, DateiImportBerichtCustomData customDateiImportBericht, StringBuilder bericht) {
    Wahldaten<Wahl, Gebiet> wahldaten = wahlImportObject.getWahldaten();
    Wahl wahl = wahldaten.getWahl();

    if (wahlkonfigurationAnpassbar.isWahlkonfigurationAnpassbar(wahlImportContext.getMandant()) && wahlImportObject.isWahlkonfigurationVorhanden()) {
      List<Gebietsarteigenschaft> gebietsarteigenschaften = new ArrayList<>(wahl.getWahlart().getGebietsarteigenschaften());
      List<Gebietsart> gebietsarten = gebietsarteigenschaften.stream().map(Gebietsarteigenschaft::getGebietsart).collect(Collectors.toList());

      wahlKonfigurationImporter.importiereImportObject(new WahlKonfiguration(singletonList(wahl.getWahlart()), gebietsarten, gebietsarteigenschaften),
          new WahlKonfigurationExchangeContext(wahlImportContext.getMandant()), ImportOptions.NEUE_DATEN_ANLEGEN_BESTEHENDE_DATEN_UNVERAENDERT_LASSEN, new DateiImportBerichtCustomData(), bericht);

      wahlDAO.flush(); // Damit Wahlarten gefunden werden
    }

    updateWahlBeforePersist(wahlImportContext, wahl);

    if (wahlImportObject.getDokumentVorlagen() == null || wahlImportObject.getDokumentVorlagen().isEmpty()) {
      wahl.setWahlCreateDokumenteUebernehmen(WahlCreateDokumenteUebernehmen.AUS_WAHLART_UEBEREHMEN); // ELECT-8248 und ELECTKOM-99
    }
    wahlCreate.save(wahl);
    wahlDAO.flush(); // Damit später das Ergebnis der Vorperiode persistent ist, das beim Anlegen der Wahl mit angelegt wird, außerdem werden Gruppen angelegt

    List<Staat> staatenInDb = staatDAO.findByAllOrderedByNameStaatsangehoerigkeit();

    wahlImportContext.setWahlPK(wahl.getPk()); // damit in der Oberfläche die importierte Wahl gleich ausgewählt werden kann

    aktualisiereWahlparameter(wahlImportObject.getWahlParameters(), wahl);
    postCreateWahl(wahl, wahlImportContext);

    Wahltermin wahltermin = wahl.getWahltermin();

    persistiereGebiete(wahlImportContext.getMandant(), wahlImportObject, wahl, wahltermin);
    persistiereGebietsgruppierungen(wahldaten.getGebietsgruppierungen(), wahl);
    persistiereGruppen(wahlImportObject, wahl);
    gebietDAO.flush(); // The batch containing 17 statements could not be sorted after 34 iterations. This might indicate a circular entity relationship. - Constraintverletzung Berliner Wahl...
    persistiereGruppenImGebiet(wahlImportObject);
    persistiereWahlvorschlaege(wahlImportObject);
    persistiereKandidaturen(wahlImportObject, wahl, staatenInDb);
    persistiereFremdkandidaturen(wahldaten.getFremdkandidaturen(), wahl, staatenInDb);
    persistiereStimmen(wahldaten::getStimmen, wahl);
    persistiereGewaehltVorperiode(wahldaten.getGewaehltVorperiode(), wahl);
    persistiereSitzeVorperiode(wahldaten, wahl);
    persistiereWahlvorschlagsPersonen(wahldaten.getWahlvorschlagsPersonen(), wahl, staatenInDb);
    persistiereWahlausschussmitglieder(wahldaten.getWahlausschussmitglieder(), wahl, staatenInDb);
    aktualisiereWahlausschussEntschaedigung(wahldaten.getWahlausschussEntschaedigungen(), wahl, bericht);
    persistiereStaatenGruppen(wahldaten.getStaatenGruppen(), wahl, staatenInDb);
    persistiereGebietPolygone(wahldaten.getGebietPolygonPoints());

    persistiereDokumentVorlagen(wahldaten, wahlImportObject.getDokumentVorlagen(), bericht);
    persistiereInternetSeiten(wahldaten.getInternetSeiten(), wahl);

    persistiereWahlbeteiligungVerlaeufe(wahldaten.getWahlbeteiligungVerlaeufe(), wahl);

    persistiereStimmeingabePlausiKonfigurationen(wahldaten.getStimmeingabePlausiKonfigurationen(), wahl, bericht);

    persistiereVeranstaltungen(wahldaten, bericht);

    persistiereStimmzettel(wahldaten);

    persistiereZulassungspruefung(wahldaten);

    persistiereKandidatenportal(wahldaten);

    // ELECT-11327, Verknuepfungen erst NACH dem Persistieren aller Personendaten persistieren!!!
    persistierePersonenVerknuepfungen(wahldaten.getPersonenVerknuepfungen());

    defaultDokumentVorlagenInitEvent.sendEvent(wahl.getPk());
  }

  private void persistiereStimmzettel(Wahldaten<Wahl, Gebiet> wahldaten) {
    // nicht als Vorlage benutzen, kein Create verwendet
    for (Stimmzettel stimmzettel : wahldaten.getStimmzettel()) {
      stimmzettelDAO.persist(stimmzettel);
    }

    // nicht als Vorlage benutzen, kein Create verwendet
    for (StimmzettelKopfdatum stimmzettelKopfdatum : wahldaten.getStimmzettelKopfdaten()) {
      stimmzettelKopfdatumDAO.persist(stimmzettelKopfdatum);
    }
  }

  // ELECT-10913
  private void persistiereZulassungspruefung(Wahldaten<Wahl, Gebiet> wahldaten) {
    Wahl wahl = wahlDAO.attach(wahldaten.getWahl());

    pruefkriteriumDAO.flush(); // DB-Änderungen für Finder sichbar machen...

    Map<PruefkriteriumTK, Pruefkriterium> pruefkriteriumInDbPerPruefkriteriumTk = PruefkriteriumTK.mappePruefkriteriumNachTK(pruefkriteriumDAO.findByWahlOrderByPosition(wahl));

    for (Pruefkriterium pruefkriteriumInDatei : wahldaten.getPruefkriterien()) {
      pruefkriteriumInDatei.setWahl(wahl);

      Pruefkriterium pruefkriteriumInDb = pruefkriteriumInDbPerPruefkriteriumTk.get(pruefkriteriumInDatei.getPruefkriteriumTK());

      // NOTE, dass bereits Prüfkriterien automatisch mit der Wahl angelegt werden, cf. DefaultPruefkriterienAnlegenListener!
      if (pruefkriteriumInDb == null) {
        pruefkriteriumCreate.save(pruefkriteriumInDb);
      } else {
        pruefkriteriumInDatei.copyEntityAttributesUsingSetter(pruefkriteriumInDb);

        pruefkriteriumDAO.persist(pruefkriteriumInDb);
      }
    }

    for (Wahlrechtspruefung wahlrechtspruefungInDatei : wahldaten.getWahlrechtspruefungen()) {
      Pruefkriterium pruefkriteriumInDatei = wahlrechtspruefungInDatei.getPruefkriterium();
      pruefkriteriumInDatei.setWahl(wahl);

      wahlrechtspruefungInDatei.setPruefkriterium(pruefkriteriumInDbPerPruefkriteriumTk.get(pruefkriteriumInDatei.getPruefkriteriumTK()));

      wahlrechtspruefungCreate.save(wahlrechtspruefungInDatei);
    }
  }

  // ELECT-11642
  private void persistiereKandidatenportal(Wahldaten<Wahl, Gebiet> wahldaten) {
    for (WvpWarnung wvpWarnungInDatei : wahldaten.getWvpWarnungen()) {
      wvpWarnungCreate.save(wvpWarnungInDatei);
    }
  }

  private void aktualisiereWahlparameter(List<WahlParameter> wahlParameters, Wahl wahl) {
    // ELECT-8305
    if (wahlParameters == null) {
      return;
    }

    for (WahlParameter wahlParameter : wahlParameters) {
      // nur gesetzte Werte übernehmen, um keine Default-Werte zu überschreiben
      if (wahlParameter != null && isNotBlank(wahlParameter.getKey()) && isNotBlank(wahlParameter.getWert())) {
        WahlParameterWerte wahlParameterWerte = bestimmeWahlParameterWerte(wahlParameter.getKey());

        if (wahlParameterWerte != null) {
          WahlParameter parameter = wahlParameterLoader.getParameter(wahl, wahlParameterWerte);

          if (parameter == null) {
            LOG.info("Wahlparameter wird nicht importiert: " + wahlParameter);
          } else {
            parameter.resetAttributeChanged();
            String alterWert = parameter.getColumnDisplayValue(getMessages(), getUserLocale());
            parameter.setWert(wahlParameter.getWert());

            if (isLoggeVeraenderteWahlparameter() && parameter.hasChangedWert()) {
              if (isBlank(alterWert)) {
                alterWert = getMessage("wahl.import.wahlparameter.nicht.gesetzt");
              }
              String alterWertFinal = alterWert;
              parameter.getTemplate().ifPresent(
                  parameterWerte -> systemLogWriter.writeInfoLogMessageFuerMandant(wahl.getMandant(), getMessage("wahl.import.wahlparameter.angepasst", getMessage(parameterWerte), alterWertFinal,
                      parameter.getColumnDisplayValue(getMessages(), getUserLocale()), wahl.getName(), getUserLocale().formatDate(wahl.getWahldatum())), SystemLogQuelle.KONFIGURATION));
            }
          }
        }
      }
    }
  }

  protected boolean isLoggeVeraenderteWahlparameter() {
    return true;
  }

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

  private void persistiereStimmeingabePlausiKonfigurationen(List<StimmeingabePlausiKonfiguration> stimmeingabePlausiKonfigurationen, Wahl wahl, StringBuilder bericht) {
    if (!stimmeingabePlausiKonfigurationen.isEmpty()) {
      wahlDAO.flush();
      stimmeingabePlausiKonfigurationDAO.flush();
      for (StimmeingabePlausiKonfiguration plausiKonfigurationInDatei : stimmeingabePlausiKonfigurationen) {
        List<StimmeingabePlausiKonfiguration> plausiKonfigurationenInDatenbank = stimmeingabePlausiKonfigurationDAO.findByWahlAndErgebniseingangsartAndGebietsartAndTyp(wahl,
            plausiKonfigurationInDatei.getErgebniseingangsart(), plausiKonfigurationInDatei.getGebietsart(), plausiKonfigurationInDatei.getTyp());
        if (plausiKonfigurationenInDatenbank.isEmpty()) {
          plausiKonfigurationInDatei.setWahl(wahl);
          stimmeingabePlausiKonfigurationCreate.save(plausiKonfigurationInDatei);
        } else {
          for (StimmeingabePlausiKonfiguration plausiKonfigurationInDatenbank : plausiKonfigurationenInDatenbank) {
            if (!isBlank(plausiKonfigurationInDatei.getBeschreibung())) {
              plausiKonfigurationInDatenbank.setBeschreibung(plausiKonfigurationInDatei.getBeschreibung());
            }
            if (!isBlank(plausiKonfigurationInDatei.getFehlermeldungFormat())) {
              plausiKonfigurationInDatenbank.setFehlermeldungFormat(plausiKonfigurationInDatei.getFehlermeldungFormat());
            }
            plausiKonfigurationInDatenbank.setFehlerart(plausiKonfigurationInDatei.getFehlerart());
            if (!isBlank(plausiKonfigurationInDatei.getFehlerschluessel())) {
              plausiKonfigurationInDatenbank.setFehlerschluessel(plausiKonfigurationInDatei.getFehlerschluessel());
            }
            plausiKonfigurationInDatenbank.setStatus(plausiKonfigurationInDatei.getStatus());
            if (!isBlank(plausiKonfigurationInDatei.getCustomDataXml())) {
              plausiKonfigurationInDatenbank.setCustomDataXml(plausiKonfigurationInDatei.getCustomDataXml());
            }
          }
        }
      }
    }
  }

  private void persistiereWahlbeteiligungVerlaeufe(List<WahlbeteiligungVerlauf> wahlbeteiligungVerlaeufe, Wahl wahl) {
    if (!wahlbeteiligungVerlaeufe.isEmpty()) {
      wahlDAO.flush();

      Map<WahlbeteiligungVerlaufTK, List<WahlbeteiligungVerlauf>> collect = wahlbeteiligungVerlaufDAO.findByWahl(wahl).stream()
          .collect(Collectors.groupingBy(WahlbeteiligungVerlauf::getWahlbeteiligungVerlaufTK));

      for (WahlbeteiligungVerlauf wahlbeteiligungVerlauf : wahlbeteiligungVerlaeufe) {
        wahlbeteiligungVerlauf.setWahl(wahl);
        for (WahlbeteiligungVerlauf vorhanden : collect.getOrDefault(wahlbeteiligungVerlauf.getWahlbeteiligungVerlaufTK(), emptyList())) {
          wahlbeteiligungVerlaufDAO.remove(vorhanden);
        }
        wahlbeteiligungVerlaufCreate.save(wahlbeteiligungVerlauf);
      }
    }
  }

  private void persistiereInternetSeiten(List<InternetSeite> internetSeiten, Wahl wahl) {
    internetKonfigurationImporter.persistieren(wahl, internetSeiten);
  }

  private void validiereDokumentVorlagen(Wahldaten<Wahl, Gebiet> wahldaten, List<DokumentVorlage> dokumentVorlagen, Violations violations) {
    DokumentVorlageExchangeContext context = createDokumentVorlageExchangeContext(wahldaten);
    dokumentVorlageImporter.validiere(violations, dokumentVorlagen, context, DokumentVorlageImporter.DEFAULT_OPTIONS);
  }

  private void persistiereDokumentVorlagen(Wahldaten<Wahl, Gebiet> wahldaten, List<DokumentVorlage> dokumentVorlagen, StringBuilder bericht) {
    DokumentVorlageExchangeContext context = createDokumentVorlageExchangeContext(wahldaten);
    dokumentVorlageImporter.importiereImportObject(dokumentVorlagen, context, DokumentVorlageImporter.DEFAULT_OPTIONS, new DateiImportBerichtCustomData(), bericht);
  }

  private DokumentVorlageExchangeContext createDokumentVorlageExchangeContext(Wahldaten<Wahl, Gebiet> wahldaten) {
    return new DokumentVorlageExchangeContext(wahldaten.getWahl());
  }

  private void persistiereGebietPolygone(List<GebietPolygonPoint> gebietPolygonPoints) {
    for (GebietPolygonPoint gebietPolygonPoint : gebietPolygonPoints) {
      gebietPolygonPointDAO.persist(gebietPolygonPoint);
    }
  }

  private void persistiereFremdkandidaturen(List<Fremdkandidatur> fremdkandidaturen, Wahl wahl, List<Staat> staatenInDb) {
    for (Fremdkandidatur fremdkandidatur : fremdkandidaturen) {
      persistierePersonendaten(fremdkandidaturCreate::save, fremdkandidatur, wahl, staatenInDb);
    }
  }

  private void aktualisiereWahlausschussEntschaedigung(List<WahlausschussEntschaedigung> wahlausschussEntschaedigungen, Wahl wahl, StringBuilder bericht) {
    Map<WahlausschussEntschaedigungTK, WahlausschussEntschaedigung> persistent = WahlausschussEntschaedigungTK.mappeWahlausschussEntschaedigungNachTK(wahlausschussEntschaedigungDAO.findByWahl(wahl));
    for (WahlausschussEntschaedigung wahlausschussEntschaedigungExtern : wahlausschussEntschaedigungen) {
      WahlausschussEntschaedigung wahlausschussEntschaedigungIntern = persistent.get(wahlausschussEntschaedigungExtern.getWahlausschussEntschaedigungTK());
      if (wahlausschussEntschaedigungIntern == null) {
        bericht.append("WahlausschussEntschaedigung: ").append(wahlausschussEntschaedigungExtern.getTk()).append(" nicht gefunden - ignoriert\n");
      } else {
        wahlausschussEntschaedigungIntern.setWaehrungAlsIsoCode(wahlausschussEntschaedigungExtern.getWaehrungAlsIsoCode());
        wahlausschussEntschaedigungIntern.setBetrag(wahlausschussEntschaedigungExtern.getBetrag());
      }
    }
  }

  private void persistiereWahlvorschlagsPersonen(List<WahlvorschlagsPerson> wahlvorschlagsPersonen, Wahl wahl, List<Staat> staatenInDb) {
    for (WahlvorschlagsPerson wahlvorschlagsPerson : wahlvorschlagsPersonen) {
      persistierePersonendaten(wahlvorschlagsPersonCreate::save, wahlvorschlagsPerson, wahl, staatenInDb);
    }
  }

  private void persistiereWahlausschussmitglieder(List<Wahlausschussmitglied> wahlausschussmitglieder, Wahl wahl, List<Staat> staatenInDb) {
    for (Wahlausschussmitglied wahlausschussmitglied : wahlausschussmitglieder) {
      persistierePersonendaten(wahlausschussmitgliedCreate::save, wahlausschussmitglied, wahl, staatenInDb);
    }
  }

  private void persistiereStaatenGruppen(List<StaatenGruppe> staatenGruppen, Wahl wahl, List<Staat> staatenInDb) {
    for (StaatenGruppe staatenGruppe : staatenGruppen) {
      staatenGruppe.setWahl(wahl);
      staatenGruppe.setWahltermin(wahl.getWahltermin());

      List<Staat> staatenDerGruppe = new ArrayList<>(staatenGruppe.getStaats());

      staatenGruppe.removeAllStaats();

      for (Staat staat : attachAll(staatenDerGruppe, staatenInDb)) {
        staatenGruppe.addStaat(staat);
      }

      staatenGruppeCreate.save(staatenGruppe);
    }
  }

  private void persistiereGebietsgruppierungen(List<Gebietsgruppierung> gebietsgruppierungen, Wahl wahl) {
    for (Gebietsgruppierung gebietsgruppierung : gebietsgruppierungen) {
      gebietsgruppierung.setWahl(wahl);
      gebietsgruppierung.setWahltermin(wahl.getWahltermin());

      gebietsgruppierungDAO.persist(gebietsgruppierung);
    }
  }

  protected void updateWahlBeforePersist(WahlImportContext context, Wahl wahl) {
    wahl.getWahltermin().setMandant(context.getMandant());
    wahl.setWahlart(wahlartDAO.findByName(context.getMandant(), wahl.getWahlart().getName())); /* ELECT-10989 persistente Wahlart muss frühzeitig genommen werden */
  }

  private void persistiereStimmen(Function<Ergebnisart, List<Stimmen>> stimmenNachErgebnisartFct, Wahl wahl) {
    Map<Ergebnisart, Ergebnis> ergebnisseNachArt = ergebnisDAO.findByWahl(wahl).stream().collect(Collectors.toMap(Ergebnis::getErgebnisart, identity()));
    // nur Stimmen für vorhandene Ergebnisse anlegen ===> Woher sollen die Ergebnisse kommen?
    for (Map.Entry<Ergebnisart, Ergebnis> e : ergebnisseNachArt.entrySet()) {
      Ergebnisart ergebnisart = e.getKey();
      Ergebnis ergebnis = e.getValue();
      for (Stimmen stimmen : stimmenNachErgebnisartFct.apply(ergebnisart)) {
        stimmen.setErgebnis(ergebnis);
        stimmenDAO.persist(stimmen);
      }
    }
  }

  private void persistiereGewaehltVorperiode(List<Gewaehlt> gewaehltVorperiode, Wahl wahl) {
    Ergebnis ergebnisVorperiode = AbstractErgebnis.getErgebnisZurWahlNachErgebnisart(Ergebnisart.VORPERIODE, wahl, ergebnisDAO); // Ergebnis sollte wegen flush nach dem Wahl anlegen bereits persistent sein

    for (Gewaehlt gewaehlt : gewaehltVorperiode) {
      gewaehlt.setErgebnis(ergebnisVorperiode);

      gewaehltDAO.persist(gewaehlt);
    }
  }

  private void persistiereSitzeVorperiode(Wahldaten<Wahl, Gebiet> wahldaten, Wahl wahl) {
    Ergebnis ergebnisVorperiode = AbstractErgebnis.getErgebnisZurWahlNachErgebnisart(Ergebnisart.VORPERIODE, wahl, ergebnisDAO); // Ergebnis sollte wegen flush nach dem Wahl anlegen bereits persistent sein

    for (Sitze sitze : wahldaten.getSitzeVorperiode()) {
      sitze.setErgebnis(ergebnisVorperiode);

      persistiereSitze(sitze, wahldaten.getWahlvorschlaege());
    }
  }

  protected void persistiereSitze(Sitze sitze, List<Wahlvorschlag> wahlvorschlaege) {
    sitzeDAO.persist(sitze);
  }

  private void persistierePersonenVerknuepfungen(List<PersonenVerknuepfung> personenVerknuepfungen) {
    for (PersonenVerknuepfung personenVerknuepfung : personenVerknuepfungen) {
      personenVerknuepfungDAO.persist(personenVerknuepfung);
    }
  }

  protected void postCreateWahl(Wahl wahl, WahlImportContext wahlImportContext) {
    if (wusFeaturesAktiviert.isWusOderWusWasZusammen()) {
      // Gruppen, die automatisch angelegt wurden, gleich wieder löschen, sind nicht relevant, da in Importdaten enthalten
      for (Gruppe gruppe : gruppeDAO.findByWahl(wahl)) {
        gruppeDAO.remove(gruppe);
      }

      gruppeDAO.flush();
    }
  }

  protected void persistiereKandidaturen(WahlImportObject wahlImportObject, Wahl wahl, List<Staat> staatenInDb) {
    for (Kandidatur kandidatur : wahlImportObject.getWahldaten().getKandidaturen()) {
      if (!kandidatur.isErsatzkandidatur()) {
        persistiereKandidaturMitUntergeordnetenErsatzkandidaturen(kandidatur, wahl, staatenInDb);
      }
    }
  }

  protected void persistiereErsatzkandidatur(Ersatzkandidatur ersatzkandidatur, Kandidatur persistierteKandidatur, List<Staat> staatenInDb) {
    ersatzkandidatur.setUebergeordneteKandidatur(persistierteKandidatur);

    persistiereKandidaturMitUntergeordnetenErsatzkandidaturen(ersatzkandidatur, persistierteKandidatur.getWahl(), staatenInDb);
  }

  protected void persistiereKandidaturMitUntergeordnetenErsatzkandidaturen(Kandidatur kandidatur, Wahl wahl, List<Staat> staatenInDb) {
    persistierePersonendaten(kandidaturCreate::save, kandidatur, wahl, staatenInDb);

    if (kandidatur.hasZugeordneteErsatzkandidatur()) {
      persistiereErsatzkandidatur(kandidatur.getZugeordneteErsatzkandidatur(), kandidatur, staatenInDb);
    }
  }

  private <T extends Personendaten> void persistierePersonendaten(Consumer<T> saveFct, T personendaten, Wahl wahl, List<Staat> staatenInDb) {
    personendaten.setWahl(wahl);
    attachStaaten(personendaten, staatenInDb); // ELECTMUC-1317
    saveFct.accept(personendaten);
  }

  // NOTE, dass die übergebenen Staaten nicht persistiert werden, sondern die im System bereits vorhandenen Staaten wiederverwendet werden (müssen)!
  private void attachStaaten(Personendaten personendaten, List<Staat> staatenInDb) {
    personendaten.setStaatsangehoerigkeitenSorted(attachAll(personendaten.getStaatsangehoerigkeitenSorted(), staatenInDb));
  }

  protected void persistiereWahlvorschlaege(WahlImportObject wahlImportObject) {
    for (Wahlvorschlag wahlvorschlag : wahlImportObject.getWahldaten().getWahlvorschlaege()) {
      wahlvorschlagCreate.save(wahlvorschlag);
    }
  }

  protected void persistiereGruppenImGebiet(WahlImportObject wahlImportObject) {
    List<GruppeImGebiet> gruppenImGebiet = wahlImportObject.getWahldaten().getGruppenImGebiet();

    preProcessGruppenImGebiet(gruppenImGebiet);

    for (GruppeImGebiet gruppeImGebiet : gruppenImGebiet) {
      if (isPersistiereGruppeImGebiet(gruppeImGebiet)) {
        gruppeImGebietCreate.save(gruppeImGebiet);
      }
    }
  }

  protected boolean isPersistiereGruppeImGebiet(GruppeImGebiet gruppeImGebiet) {
    return true;
  }

  protected void preProcessGruppenImGebiet(List<GruppeImGebiet> gruppenImGebiet) {
    // nichts zu tun
  }

  protected void persistiereGruppen(WahlImportObject wahlImportObject, Wahl wahl) {
    List<Gruppe> gruppen = wahlImportObject.getWahldaten().getGruppen();

    AbstractGruppe.relativePositionSetzenWennNoetig(gruppen, Collections.emptyList());

    for (Gruppe gruppe : gruppen) {
      gruppe.setWahl(wahl);

      if (gruppe.getLogo() != null) {
        bildDAO.persist(gruppe.getLogo());
      }

      // ELECTSL-34
      if (gruppe.getFarbe() == null) {
        gruppe.setFarbe(new Gruppe(EmptyCustomData.INSTANCE).getFarbe());
      }

      gruppeDAO.persist(gruppe);
    }
  }

  protected void persistiereGebiete(Mandant mandant, WahlImportObject wahlImportObject, Wahl wahl, Wahltermin wahltermin) {
    List<Gebiet> gebiete = filterZuImportierendeGebiete(wahlImportObject.getWahldaten(), wahl.getWahlart());
    gebiete.sort(new GebietByBreadthFirstComparator()); // uebergeordnete Gebiete nach vorne, damit diese zuerst persistiert werden
    Map<String, Gebietsart> gebietsartNamePersistenteGebietsartMap = new HashMap<>();
    Map<String, Wahlart> wahlartNamePersistenteWahlartMap = new HashMap<>();

    Map<String, Rolle> rollenInDb = gebietsbaumImporter.sucheRollenInDb(mandant);
    Map<Long, Mandant> mandantenInDb = gebietsbaumImporter.sucheMandanten();

    for (Gebiet gebiet : gebiete) {
      gebiet.setWahl(wahl);

      updateGebietsart(gebiet, mandant, gebietsartNamePersistenteGebietsartMap, wahlartNamePersistenteWahlartMap);

      gebietsbaumImporter.tauscheRollen(mandant, gebiet, rollenInDb);
      gebietsbaumImporter.tauscheMandanten(gebiet, mandantenInDb, null /* irrelevant */);

      gebietCreate.save(gebiet);
      postCreateGebiet(gebiet);
    }
    postCreateGebiete(gebiete);
  }

  protected void postCreateGebiete(List<Gebiet> gebiete) {
    // nichts zu tun
  }

  protected void updateGebietsart(Gebiet gebiet, Mandant mandant, Map<String, Gebietsart> gebietsartNamePersistenteGebietsartMap, Map<String, Wahlart> wahlartNamePersistenteWahlartMap) {
    String gebietsartName = gebiet.getGebietsart().getName();
    GebietsartUK gebietsartUK = new GebietsartUK(gebietsartName, mandant.getMandantUK());
    Gebietsart persistenteGebietsart = gebietsartNamePersistenteGebietsartMap.computeIfAbsent(gebietsartName, k -> gebietsartDAO.findByUK(gebietsartUK));
    gebiet.setGebietsart(persistenteGebietsart);
  }

  protected void postCreateGebiet(Gebiet gebiet) {
    // nichts zu tun
  }

  private void validiereVeranstaltungen(Wahldaten<Wahl, Gebiet> wahldaten, Violations violations) {
    veranstaltungImporter.validiere(violations, wahldaten.getVeranstaltungen(), createVeranstaltungExchangeContext(wahldaten), ImportOptions.NEUE_DATEN_ANLEGEN_BESTEHENDE_DATEN_UNVERAENDERT_LASSEN);
  }

  private void persistiereVeranstaltungen(Wahldaten<Wahl, Gebiet> wahldaten, StringBuilder bericht) {
    veranstaltungImporter.importiereImportObject(wahldaten.getVeranstaltungen(), createVeranstaltungExchangeContext(wahldaten), ImportOptions.NEUE_DATEN_ANLEGEN_BESTEHENDE_DATEN_UNVERAENDERT_LASSEN,
        new DateiImportBerichtCustomData(), bericht);
  }

  private VeranstaltungExchangeContext createVeranstaltungExchangeContext(Wahldaten<Wahl, Gebiet> wahldaten) {
    Wahl wahl = wahldaten.getWahl();
    VeranstaltungExchangeContext context = new VeranstaltungExchangeContext(wahl.getWahltermin(), wahl, wahldaten.getWurzelgebiet());

    // HINT: Der WahlImporter übergibt dem VeranstaltungImporter Teilnahmen mit persistenten Personendaten.
    context.setTeilnahmePersonendatenSindBereitsPersistent(true);

    return context;
  }

  @Override
  protected void aktualisiereBestehendenDaten(WahlImportObject wahlImportObject, WahlImportContext noContext, DateiImportBerichtCustomData customDateiImportBericht, StringBuilder bericht) {
    throw new UnsupportedOperationException("Operation nicht unterstützt");
  }

  @Override
  protected void loescheBestehendenDatenWennNichtInImportDatenVorhanden(WahlImportObject wahlImportObject, WahlImportContext noContext, DateiImportBerichtCustomData customDateiImportBericht,
      StringBuilder bericht) {
    throw new UnsupportedOperationException("Operation nicht unterstützt");
  }

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

  private List<Staat> attachAll(List<Staat> staatsangehoerigkeiten, List<Staat> staatenInDb) {
    return staatsangehoerigkeiten.stream() //
        .map(staatsangehoerigkeit -> getExistingStaat(staatsangehoerigkeit.getIso3166Alpha2(), staatsangehoerigkeit.getCustomCodeStaat(), staatsangehoerigkeit.getCustomCodeStaatsangehoerigkeit(),
            staatenInDb)) //
        .filter(Objects::nonNull) //
        .collect(Collectors.toList());
  }

  @Nullable
  protected Staat getExistingStaat(@Nullable String iso3166Alpha2, @Nullable String customCodeStaat, @Nullable String customCodeStaatsangehoerigkeit, List<Staat> staatenInDb) {
    return staatenInDb.stream().filter(staatsangehoerigkeit -> staatsangehoerigkeit.hasCodes(iso3166Alpha2, customCodeStaat, customCodeStaatsangehoerigkeit)).findFirst().orElse(null);
  }

  @Override
  protected WahlImportObject readImportObject(DateiImport dateiImport, WahlImportContext wahlImportContext) throws MappingException {
    WahlImportObject wahlImportObject = super.readImportObject(dateiImport, wahlImportContext);
    if (wahlImportObject != null && wahlImportObject.getWahldaten() != null && wahlImportObject.getWahldaten().getWahl() != null && wahlImportObject.getWahldaten().getWahl().getWahltermin() != null) {

      if (wahlImportContext.getNeuesDatum() != null) {
        wahlImportObject.getWahldaten().getWahl().getWahltermin().setWahldatum(wahlImportContext.getNeuesDatum());
      }
      if (wahlImportContext.getNeueNummer() != null) {
        wahlImportObject.getWahldaten().getWahl().getWahltermin().setNummer(wahlImportContext.getNeueNummer());
      }
    }

    return wahlImportObject;
  }
}
