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

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

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

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

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

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

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

  public static final String FILEFORMAT = "FILEFORMATKEY";

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

  @Inject
  private transient ErgebniseingangMeta ergebniseingangMeta;

  @Inject
  private transient StimmeingabeImportExchange stimmeingabeImportExchange;

  @Inject
  private transient ErgebniseingangDAO ergebniseingangDAO;

  @Inject
  private transient WahldurchfuehrungDAO wahldurchfuehrungDAO;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  private transient WahlvorschlagDAO wahlvorschlagDAO;

  @Inject
  private transient GruppeImGebietDAO gruppeImGebietDAO;

  @Inject
  private transient ListenkandidaturDAO listenkandidaturDAO;

  @Inject
  private transient ReferendumOptionDAO referendumOptionDAO;

  @Inject
  private transient StimmeingabeDAO stimmeingabeDAO;

  @Inject
  private transient StimmeingabefehlerDAO stimmeingabefehlerDAO;

  @Inject
  private transient ErgebnissequenznummerSetzenEvent ergebnissequenznummerSetzenEvent;

  @Inject
  private transient StimmeingabeCreate stimmeingabeCreate;

  @Inject
  private transient StimmeingabeErfassenDescriptionResolver stimmeingabeErfassenDescriptionResolver;

  @Inject
  private transient ErgebniseingangPlausiFehlerBeheber ergebniseingangPlausiFehlerBeheber;

  @Inject
  private transient ErgebniseingangEingangssequenznummerGenerator ergebniseingangEingangssequenznummerGenerator;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient GebietDAO gebietDAO;

  @Inject
  private transient StimmeingabeImporterHelper stimmeingabeImporterHelper;

  @Inject
  private transient WahlaufteilenStimmeingabeCreate wahlaufteilenStimmeingabeCreate;

  @Inject
  private transient EntityAufStimmzettelDatenDAOFactory entityAufStimmzettelDatenDAOFactory;

  public StimmeingabeImporter() {
    super();
  }

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

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

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

    return super.readImportObject(dateiImport, context);
  }

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

    Map<GebietTK, Wahldurchfuehrungsgebiet> gebieteInDB = getGebieteInDB(wahldurchfuehrung, neueEntities);

    List<GruppeImGebiet> gruppenImGebietInWahl = ladeGruppeImGebiet(wahldurchfuehrung, neueEntities);
    Map<GruppeImGebietTK, GruppeImGebiet> gruppeImGebietInWahlPerTk = GruppeImGebietTK.mappeGruppeImGebietNachTK(gruppenImGebietInWahl);

    Collection<Listenkandidatur> listenkandidaturen = listenkandidaturDAO.findByWahl(wahldurchfuehrung);

    Map<ListenkandidaturTK, Listenkandidatur> listenkandidaturenInDbNachListenkandidaturTk = ListenkandidaturTK.mappeListenkandidaturNachTK(listenkandidaturen);

    Map<ListenkandidaturNamenUndListeTK, Listenkandidatur> listenkandidaturenInDbNachNamenUndListeTk = ListenkandidaturNamenUndListeTK.mappeListenkandidaturNachTK(listenkandidaturen);

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

    Map<ReferendumOptionTK, ReferendumOption> referendumOptionenInDb = ReferendumOptionTK.mappeReferendumOptionNachTK(referendumOptionDAO.findByWahl(wahldurchfuehrung));

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

    StimmeingabeImporterGigContext gigContext = getGruppeImGebietContext(wahldurchfuehrung, gruppenImGebietInWahl);

    WahlvorschlaegeSupplier wahlvorschlaege = new WahlvorschlaegeSupplier(wahldurchfuehrung, wahlvorschlagDAO);

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

    Map<Gebiet, List<GruppeImGebiet>> gigNachGebiet = gruppenImGebietInWahl.stream().filter(gig -> gig.getGebiet() != null).collect(groupingBy(GruppeImGebiet::getGebiet));

    StimmeingabeImporterGruppeImGebietErsetzenStrategie stimmeingabeImporterGruppeImGebietErsetzenStrategie = getStimmeingabeImporterGruppeImGebietErsetzenStrategie(context);
    StimmeingabeImporterListenkandidaturErsetzenStrategie stimmeingabeImporterListenkandidaturErsetzenStrategie = getStimmeingabeImporterListenkandidaturErsetzenStrategie(context);
    StimmeingabeImporterReferendumOptionErsetzenStrategie stimmeingabeImporterReferendumOptionErsetzenStrategie = getStimmeingabeImporterReferendumOptionErsetzenStrategie(context);

    for (Ergebniseingang ergebniseingang : neueEntitiesSortiert) {
      tauscheWahldurchfuehrungsgebiet(ergebniseingang, gebieteInDB);

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

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

      if (!nichtZuPersistieren.isEmpty()) {
        LOG.info("Speichere nicht: " + nichtZuPersistieren);
        for (Stimmeingabe stimmeingabe : nichtZuPersistieren) {
          ergebniseingang.removeStimmeingabe(stimmeingabe);
          ergebniseingang.setErgebniseingangsfehlerstatus(null); // Auf jeden Fall plausibislisieren
        }
      }

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

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

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

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

        speicherePlausibilisiertenErgebniseingang(ergebniseingang);
      }
    }
  }

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

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

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

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

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

    ergebniseingangDAO.persist(ergebniseingang);

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

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

    stimmeingabeCreate.exportiereStimmeingaben(ergebniseingang);

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

  }

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

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

    return result;
  }

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

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

    return result;
  }

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

    Ergebniseingang result = createErgebniseingangZumPersistieren(ergebniseingangAusImport, context);

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

    ergebniseingangPlausiFehlerBeheber.behebePlausiFehlerByAuffuellenLeererStimmeingabenMitSollWerten(result);

    return result;
  }

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

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

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

    result.setWahldurchfuehrungsgebiet(ergebniseingang.getWahldurchfuehrungsgebiet());

    Wahldurchfuehrung wahldurchfuehrung = findWahl(context);

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

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

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

    return result;
  }

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

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

    return result;
  }

  protected void tauscheWahldurchfuehrungsgebiet(Ergebniseingang ergebniseingang, Map<GebietTK, Wahldurchfuehrungsgebiet> gebieteInDb) {
    GebietTK gebietTK = ergebniseingang.getWahldurchfuehrungsgebiet().getGebietTK();
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb = gebieteInDb.get(gebietTK);
    ergebniseingang.setWahldurchfuehrungsgebiet(wahldurchfuehrungsgebietInDb);
  }

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

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

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

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

  }

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

    List<Wahldurchfuehrungsgebiet> gebieteDerWahl = ladeGebieteDerWahl(wahldurchfuehrung, ergebniseingangs);
    Map<WahldurchfuehrungsgebietTK, Wahldurchfuehrungsgebiet> gebieteInDB = WahldurchfuehrungsgebietTK.mappeWahldurchfuehrungsgebietNachTK(gebieteDerWahl);
    Set<Wahldurchfuehrungsgebiet> betrachteteGebiete = new HashSet<>();
    List<GruppeImGebiet> gruppenImGebietInWahl = ladeGruppeImGebiet(wahldurchfuehrung, ergebniseingangs);
    Map<GruppeImGebietTK, GruppeImGebiet> gruppeImGebietInDb = GruppeImGebietTK.mappeGruppeImGebietNachTK(gruppenImGebietInWahl);
    List<Listenkandidatur> listenkandidaturenInDb = listenkandidaturDAO.findByWahl(wahldurchfuehrung);

    Map<ListenkandidaturTK, Listenkandidatur> listenkandidaturenInDbNachListenkandidaturTk = ListenkandidaturTK.mappeListenkandidaturNachTK(listenkandidaturenInDb);

    Set<Listenkandidatur> betrachteteListenkandidaturen = new HashSet<>();
    Map<ListenkandidaturNamenUndListeTK, Listenkandidatur> listenkandidaturenInDbNachNamenUndListeTk = ListenkandidaturNamenUndListeTK.mappeListenkandidaturNachTK(listenkandidaturenInDb);
    Map<ReferendumOptionTK, ReferendumOption> referendumOptionenInDb = ReferendumOptionTK.mappeReferendumOptionNachTK(referendumOptionDAO.findByWahl(wahldurchfuehrung));
    Set<ReferendumOption> betrachteteReferendumOptionen = new HashSet<>();

    StimmeingabeImporterGigContext gigInDbContext = getGruppeImGebietContext(wahldurchfuehrung, gruppenImGebietInWahl);

    StimmeingabeImporterGruppeImGebietErsetzenStrategie stimmeingabeImporterGruppeImGebietErsetzenStrategie = getStimmeingabeImporterGruppeImGebietErsetzenStrategie(context);
    StimmeingabeImporterListenkandidaturErsetzenStrategie stimmeingabeImporterListenkandidaturErsetzenStrategie = getStimmeingabeImporterListenkandidaturErsetzenStrategie(context);
    StimmeingabeImporterReferendumOptionErsetzenStrategie stimmeingabeImporterReferendumOptionErsetzenStrategie = getStimmeingabeImporterReferendumOptionErsetzenStrategie(context);

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

      pruefeGebietVorhanden(violations, gebieteInDB, betrachteteGebiete, ergebniseingang);

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

      for (Stimmeingabe stimmeingabe : ergebniseingang.getStimmeingaben()) {
        stimmeingabeImporterGruppeImGebietErsetzenStrategie.pruefeGruppeImGebiet(wahldurchfuehrung, violations, gruppeImGebietInDb, stimmeingabe, gigInDbContext, context, stimmeingabeImporterHelper);
        pruefeListenkandidatur(violations, wahldurchfuehrung, wahldurchfuehrungsgebietInDb, listenkandidaturenInDbNachListenkandidaturTk, listenkandidaturenInDbNachNamenUndListeTk,
            betrachteteListenkandidaturen, stimmeingabe, stimmeingabeImporterListenkandidaturErsetzenStrategie, gruppeImGebietInDb, gigInDbContext, stimmeingabeImporterHelper);
        pruefeReferendumOptionVorhanden(violations, referendumOptionenInDb, betrachteteReferendumOptionen, stimmeingabe, stimmeingabeImporterReferendumOptionErsetzenStrategie, gruppeImGebietInDb,
            gigInDbContext, stimmeingabeImporterHelper);
      }
    }
  }

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

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

    return gruppeImGebietDAO.findByWahl(wahldurchfuehrung);
  }

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

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

  private void pruefeListenkandidatur(Violations violations, Wahldurchfuehrung wahldurchfuehrung, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb,
      Map<ListenkandidaturTK, Listenkandidatur> listenkandidaturenInDbNachListenkandidaturTk, Map<ListenkandidaturNamenUndListeTK, Listenkandidatur> listenkandidaturenInDbNachNamenUndListeTk,
      Set<Listenkandidatur> betrachteteListenkandidaturen, Stimmeingabe stimmeingabe, StimmeingabeImporterListenkandidaturErsetzenStrategie stimmeingabeImporterListenkandidaturErsetzenStrategie,
      Map<GruppeImGebietTK, GruppeImGebiet> gruppeImGebietInDb, StimmeingabeImporterGigContext gigInDbContext, StimmeingabeImporterHelper stimmeingabeImporterHelper) {
    Listenkandidatur listenkandidatur = stimmeingabe.getListenkandidatur();
    if (listenkandidatur != null) {
      if (!betrachteteListenkandidaturen.contains(listenkandidatur)) {
        betrachteteListenkandidaturen.add(listenkandidatur);
        if (!stimmeingabeImporterListenkandidaturErsetzenStrategie.isListenkandidaturVorhanden(wahldurchfuehrungsgebietInDb, listenkandidaturenInDbNachListenkandidaturTk,
            listenkandidaturenInDbNachNamenUndListeTk, listenkandidatur, gruppeImGebietInDb, gigInDbContext, stimmeingabeImporterHelper)) {
          Long gruppenposition = null;
          if (listenkandidatur.getGruppeImGebiet() != null) {
            gruppenposition = listenkandidatur.getGruppeImGebiet().getVorgegebenePosition();
          }
          if (wahldurchfuehrung.isWahlMitListenkandidatenStimmen()) {
            violations.addError("stimmeingabe.import.listenkandidatur.vorhanden", listenkandidatur.getListenkandidaturTK(), listenkandidatur.getVorgegebenePosition(), gruppenposition);
          } else {
            violations.addWarning("stimmeingabe.import.listenkandidatur.vorhanden", listenkandidatur.getListenkandidaturTK(), listenkandidatur.getVorgegebenePosition(), gruppenposition);
          }
        }
      }
    }
  }

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

  private void pruefeGebietVorhanden(Violations violations, Map<WahldurchfuehrungsgebietTK, Wahldurchfuehrungsgebiet> gebieteInDB, Set<Wahldurchfuehrungsgebiet> betrachteteGebiete,
      Ergebniseingang ergebniseingang) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDatei = ergebniseingang.getWahldurchfuehrungsgebiet();
    if (!betrachteteGebiete.contains(wahldurchfuehrungsgebietInDatei)) {
      betrachteteGebiete.add(wahldurchfuehrungsgebietInDatei);
      stimmeingabeImporterHelper.preGebietCheck(gebieteInDB, wahldurchfuehrungsgebietInDatei);
      if (!gebieteInDB.containsKey(wahldurchfuehrungsgebietInDatei.getWahldurchfuehrungsgebietTK())) {
        violations.addError("stimmeingabe.import.gebiet.nicht.vorhanden", wahldurchfuehrungsgebietInDatei.getWahldurchfuehrungsgebietTK(),
            wahldurchfuehrungsgebietInDatei.getNummerBezirkFallsGesetzt());
      }
    }
  }

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

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

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

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

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

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

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

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

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

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

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