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

import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

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.WusFeaturesAktiviert;
import de.ivu.elect.business.datei.entity.DateiImport;
import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.internet.entity.PraesentationGenerierenEvent;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.replikation.boundary.ReplikationErzeugen;
import de.ivu.elect.business.sitzberechnung.control.Sitzberechnungsalgorithmus;
import de.ivu.elect.business.sitzberechnung.control.SitzberechnungsalgorithmusFinder;
import de.ivu.elect.business.staat.entity.Staat;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeImporter;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeImportContext;
import de.ivu.elect.business.stimmeingabe.control.sequenznummer.ErgebnissequenznummerSetzenEvent;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.wahl.control.WahlImportObject;
import de.ivu.elect.business.wahl.control.Wahldaten;
import de.ivu.elect.business.wahl.control.WasWahlImportExchange;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlImportContext;
import de.ivu.elect.business.wahl.entity.Wahlart;
import de.ivu.elect.business.wahl.entity.WahlartSitzberechnungsalgorithmus;
import de.ivu.elect.business.wahl.entity.WahlartUK;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlbeteiligung.boundary.WahlbeteiligungAuswertungDAO;
import de.ivu.elect.business.wahlbeteiligung.control.WahlbeteiligungAuswertungCreate;
import de.ivu.elect.business.wahlbeteiligung.entity.WahlbeteiligungAuswertung;
import de.ivu.elect.business.wahlbeteiligung.entity.WahlbeteiligungAuswertungTK;
import de.ivu.elect.business.wahlvorschlag.control.WaehlerBenannteListeCreate;
import de.ivu.elect.jee.exchange.ImportOptions;
import de.ivu.elect.jee.exchange.Violations;

@Stateless
public class WasWahlImporter extends AbstractWahlImporter {

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

  public static final String ERGEBNISEINGAENGE_KEY = "WasWahlImporter.ERGEBNISEINGAENGE_KEY";

  @Inject
  private transient SitzberechnungsalgorithmusFinder sitzberechnungsalgorithmusFinder;

  @Inject
  private transient WasWahlImportExchange wasWahlImportExchange;

  @Inject
  private transient SystemGruppeEigenschaften systemGruppeEigenschaften;

  @Inject
  private transient WaehlerBenannteListeCreate waehlerBenannteListeCreate;

  @Inject
  private transient ReplikationErzeugen replikationErzeugen;

  @Inject
  private transient WusFeaturesAktiviert wusFeaturesAktiviert;

  @Inject
  private transient StimmeingabeImporter stimmeingabeImporter;

  @Inject
  private transient ErgebnissequenznummerSetzenEvent ergebnissequenznummerSetzenEvent;

  @Inject
  private transient PraesentationGenerierenEvent praesentationGenerierenEvent;

  @Inject
  private transient WahlbeteiligungAuswertungDAO wahlbeteiligungAuswertungDAO;

  @Inject
  private transient WahlbeteiligungAuswertungCreate wahlbeteiligungAuswertungCreate;

  public WasWahlImporter() {
    super();
  }

  @Override
  protected void postImportActions(DateiImport dateiImport, WahlImportContext wahlImportContext, ImportOptions importOptions, DateiImportBerichtCustomData customDateiImportBericht) {
    super.postImportActions(dateiImport, wahlImportContext, importOptions, customDateiImportBericht);

    replikationErzeugen.replikationWahlImport(dateiImport);
    praesentationGenerierenEvent.sendChangeEvent();
  }

  @Override
  protected void updateWahlBeforePersist(WahlImportContext context, Wahl wahl) {
    Wahldurchfuehrung wahldurchfuehrung = wahl.castTo(Wahldurchfuehrung.class).getEntity();

    super.updateWahlBeforePersist(context, wahldurchfuehrung);

    setzeDefaultSitzberechnungWennNichtGesetzt(wahldurchfuehrung);

    if (/* ELECT-7448 ELECT-7449 ELECT-7450 ELECT-7451 */ wahldurchfuehrung.getSitzberechnungsalgorithmus().isKeinSitzberechnungsalgorithmus()) {
      if (!wahldurchfuehrung.getWahlart().getSitzberechnungsalgorithmus().isKeinSitzberechnungsalgorithmus()/* ELECT-10989 */) {
        List<Sitzberechnungsalgorithmus> moeglicheAlgorithmen = sitzberechnungsalgorithmusFinder.getDefaultSitzberechnungsalgorithmusNameFuerWahl(wahldurchfuehrung);
        LOG.info("Gefundene Algorithmen: " + moeglicheAlgorithmen);
        if (moeglicheAlgorithmen.size() == 1) {
          WahlartSitzberechnungsalgorithmus sitzberechnungsalgorithmusEnum = moeglicheAlgorithmen.get(0).getSitzberechnungsalgorithmusEnum();
          if (sitzberechnungsalgorithmusEnum.equals(wahldurchfuehrung.getWahlart().getSitzberechnungsalgorithmus())) {
            wahldurchfuehrung.setSitzberechnungsalgorithmus(sitzberechnungsalgorithmusEnum);
          }
        }
      }
    }

    LOG.info("Verwendeter Algorithmus: " + wahldurchfuehrung.getSitzberechnungsalgorithmus());
  }

  @Override
  protected void validiereWahl(WahlImportContext context, Wahldaten<Wahl, Gebiet> wahldaten, Violations violations) {
    super.validiereWahl(context, wahldaten, violations);

    Wahl wahl = wahldaten.getWahl();
    Wahldurchfuehrung wahldurchfuehrung = wahl.castTo(Wahldurchfuehrung.class).getEntity();
    setzeDefaultSitzberechnungWennNichtGesetzt(wahldurchfuehrung);

    if (/* ELECT-7448 ELECT-7449 ELECT-7450 ELECT-7451 */ wahldurchfuehrung.getSitzberechnungsalgorithmus().isKeinSitzberechnungsalgorithmus()) {
      List<Sitzberechnungsalgorithmus> moeglicheAlgorithmen = sitzberechnungsalgorithmusFinder.getDefaultSitzberechnungsalgorithmusNameFuerWahl(wahl);
      if (moeglicheAlgorithmen.isEmpty()) {
        violations.addInfo("sitzberechnungsalgorithmus.nicht.gefunden");
      } else if (moeglicheAlgorithmen.size() > 1) {
        List<String> algorithmen = moeglicheAlgorithmen.stream().map(a -> getMessage(a.getSitzberechnungsalgorithmusEnum())).collect(toList());
        violations.addWarning("sitzberechnungsalgorithmus.nicht.eindeutig", algorithmen);
      }
    } else {
      if (sitzberechnungsalgorithmusFinder.getSitzberechnungsalgorithmen().stream().noneMatch(a -> wahldurchfuehrung.getSitzberechnungsalgorithmus().equals(a.getSitzberechnungsalgorithmusEnum()))) {
        violations.addError("sitzberechnungsalgorithmus.id.nicht.gefunden", wahldurchfuehrung.getSitzberechnungsalgorithmus());
      }
    }

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

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

    if (wahl.getWahlart() != null) {
      vorhandeneWahlarten.add(wahl.getWahlart().getName());
    }
    vorhandeneWahlarten.addAll(wahlartDAO.findByAllOrderByName(context.getMandant()).stream().map(Wahlart::getName).collect(Collectors.toList()));

    for (Gebiet gebiet : wahldaten.getGebiete()) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity();
      if (wahldurchfuehrungsgebiet.getWahlartBeiAufteilung() != null && StringUtils.isNotBlank(wahldurchfuehrungsgebiet.getWahlartBeiAufteilung().getName())
          && !vorhandeneWahlarten.contains(wahldurchfuehrungsgebiet.getWahlartBeiAufteilung().getName())) {
        nichtVorhandeWahlarten.add(wahldurchfuehrungsgebiet.getWahlartBeiAufteilung().getName());
      }
    }
    if (!nichtVorhandeWahlarten.isEmpty()) {
      violations.addWarning("mandant.aufteilen.gebiet.wahlarten.nicht.vorhanden", String.join(", ", nichtVorhandeWahlarten));
    }

  }

  private void setzeDefaultSitzberechnungWennNichtGesetzt(Wahldurchfuehrung wahldurchfuehrung) {
    if (wahldurchfuehrung.getSitzberechnungsalgorithmus() == null) {
      if (wahldurchfuehrung.getWahlart() != null && wahldurchfuehrung.getWahlart().getSitzberechnungsalgorithmus() != null) {
        wahldurchfuehrung.setSitzberechnungsalgorithmus(wahldurchfuehrung.getWahlart().getSitzberechnungsalgorithmus());
      } else {
        wahldurchfuehrung.setSitzberechnungsalgorithmus(WahlartSitzberechnungsalgorithmus.KEIN);
      }
    }
  }

  @Override
  protected void postImportActions(WahlImportObject wahlImportObject, WahlImportContext wahlImportContext, ImportOptions importOptions, DateiImportBerichtCustomData customDateiImportBericht,
      StringBuilder bericht) {
    super.postImportActions(wahlImportObject, wahlImportContext, importOptions, customDateiImportBericht, bericht);

    erstelleListeFuerWaehlerBenannteKandidaturen(wahlImportObject.getWahldaten());
  }

  protected void erstelleListeFuerWaehlerBenannteKandidaturen(Wahldaten<Wahl, Gebiet> wahldaten) {
    Wahldurchfuehrung wahldurchfuehrung = wahldaten.getWahl().castTo(Wahldurchfuehrung.class).getEntity();
    Sitzberechnungsalgorithmus sitzberechnungsalgorithmus = sitzberechnungsalgorithmusFinder.getSitzberechnungsalgorithmus(wahldurchfuehrung);

    waehlerBenannteListeCreate.createWaehlerBenannteListen(wahldaten, sitzberechnungsalgorithmus);
  }

  @Override
  protected void persistiereGruppenImGebiet(WahlImportObject wahlImportObject) {
    if (wusFeaturesAktiviert.isNurWas()) {
      for (GruppeImGebiet gruppeImGebiet : wahlImportObject.getWahldaten().getGruppenImGebiet()) {
        // ELECT-2065 GruppeImGebiet-Namen im "reinen" WAS nicht mehr über Gebietshierarchie auflösen
        gruppeImGebiet.setName(gruppeImGebiet.getNameAufGebiet());
        gruppeImGebiet.setKurzname(gruppeImGebiet.getKurznameAufGebiet());
        gruppeImGebiet.setZusatzbezeichnung(gruppeImGebiet.getZusatzbezeichnung());
        gruppeImGebiet.setRelativePosition(gruppeImGebiet.getRelativePositionAbgeleitet());

        // Position im WAS als vorgegeben übernehmen, um Änderungen (z.B. bei nachträglicher Nichtzulassung) zu verhindern
        if (gruppeImGebiet.getRelativePosition() != null && gruppeImGebiet.getVorgegebenePosition() == null) {
          gruppeImGebiet.setVorgegebenePosition(gruppeImGebiet.getRelativePosition());
        }
      }
    }
    super.persistiereGruppenImGebiet(wahlImportObject);
  }

  @Override
  protected void persistiereKandidaturMitUntergeordnetenErsatzkandidaturen(Kandidatur kandidatur, Wahl wahl, List<Staat> staaten) {
    if (wusFeaturesAktiviert.isNurWas()) { // ELECTDD-584
      if (kandidatur.isListenkandidatur()) {
        Listenkandidatur listenkandidatur = kandidatur.castTo(Listenkandidatur.class).getEntity();

        // Position im WAS als vorgegeben übernehmen, um Änderungen (z.B. bei nachträglicher Nichtzulassung) zu verhindern
        if (listenkandidatur.getRelativePosition() != null && listenkandidatur.getVorgegebenePosition() == null) {
          listenkandidatur.setVorgegebenePosition(listenkandidatur.getRelativePosition());
        }
      }
    }

    super.persistiereKandidaturMitUntergeordnetenErsatzkandidaturen(kandidatur, wahl, staaten);
  }

  @Override
  protected void preProcessGruppenImGebiet(List<GruppeImGebiet> gruppenImGebiet) {
    super.preProcessGruppenImGebiet(gruppenImGebiet);
    for (GruppeImGebiet gruppeImGebiet : gruppenImGebiet) {
      if (gruppeImGebiet.getRelativePosition() == null && gruppeImGebiet.getGruppe().isSystemGruppe()) {
        Long relativePositionAbgeleitet = gruppeImGebiet.getRelativePositionAbgeleitet();
        if (relativePositionAbgeleitet == null) {
          gruppeImGebiet.setRelativePosition(systemGruppeEigenschaften.getPosition(SystemGruppe.getSystemGruppeFromGruppe(gruppeImGebiet.getGruppe(), systemGruppeEigenschaften)));
        } else {
          gruppeImGebiet.setRelativePosition(relativePositionAbgeleitet);
        }
      }
    }
  }

  @Override
  protected WasWahlImportExchange getExchange() {
    return wasWahlImportExchange;
  }

  @Override
  protected void anlegenWennNichtVorhanden(WahlImportObject wahlImportObject, WahlImportContext wahlImportContext, DateiImportBerichtCustomData customDateiImportBericht, StringBuilder bericht) {
    List<Ergebniseingang> ergebniseingaenge = sammleErgebniseingange(wahlImportObject);
    super.anlegenWennNichtVorhanden(wahlImportObject, wahlImportContext, customDateiImportBericht, bericht);

    if (!ergebniseingaenge.isEmpty()) {
      wahlDAO.flush();
      Wahldurchfuehrung wahldurchfuehrung = wahlImportObject.getWahldaten().getWahl().castTo(Wahldurchfuehrung.class).getEntity();
      StimmeingabeImportContext stimmeingabeImportContext = new StimmeingabeImportContext(wahldurchfuehrung);
      stimmeingabeImportContext.setPlausibilisierungKannFehlerBestaetigen(false);
      StringBuilder berichtBuilder = new StringBuilder();
      if (customDateiImportBericht != null && customDateiImportBericht.getBericht() != null) {
        berichtBuilder.append(customDateiImportBericht.getBericht());
      }
      berichtBuilder.append("\n");
      stimmeingabeImporter.persistiere(ergebniseingaenge, stimmeingabeImportContext, customDateiImportBericht, berichtBuilder);
      if (customDateiImportBericht != null) {
        customDateiImportBericht.setBericht(berichtBuilder.toString());
      }
      ergebnissequenznummerSetzenEvent.sendChangeEvent();
    }
    Wahldaten<Wahl, Gebiet> wahldaten = wahlImportObject.getWahldaten();
    Wahl wahl = wahldaten.getWahl();
    Object wahlbeteiligungAuswertungObject = wahl.getAdditionalData("WahlbeteiligungAuswertung"); // siehe de.ivu.elect.was.exchange.wahl.WasWahlExternInternMapper.mappeWahl
    if (wahlbeteiligungAuswertungObject != null) {
      List<WahlbeteiligungAuswertung> wahlbeteiligungAuswertungs = (List<WahlbeteiligungAuswertung>) wahlbeteiligungAuswertungObject;
      for (WahlbeteiligungAuswertung wahlbeteiligungAuswertung : wahlbeteiligungAuswertungs) {
        wahlbeteiligungAuswertung.setKeineReplikation(true); // ELECT-8428 Wahl mit Wahlbeteiligungen kann nicht repliziert werden
      }
      persistiereWahlbeteiligungAuswertungen(wahlbeteiligungAuswertungs, wahl.castTo(Wahldurchfuehrung.class).getEntity());
    }

  }

  private void persistiereWahlbeteiligungAuswertungen(List<WahlbeteiligungAuswertung> wahlbeteiligungAuswertungs, Wahldurchfuehrung wahl) {

    if (!wahlbeteiligungAuswertungs.isEmpty()) {
      wahlDAO.flush();

      Map<WahlbeteiligungAuswertungTK, List<WahlbeteiligungAuswertung>> collect = wahlbeteiligungAuswertungDAO.findByWahl(wahl).stream()
          .collect(Collectors.groupingBy(WahlbeteiligungAuswertung::getWahlbeteiligungAuswertungTK));
      Set<WahlbeteiligungAuswertung> zuLoeschen = new HashSet<>();

      for (WahlbeteiligungAuswertung wahlbeteiligungAuswertung : wahlbeteiligungAuswertungs) {
        wahlbeteiligungAuswertung.setWahl(wahl);
        zuLoeschen.addAll(collect.getOrDefault(wahlbeteiligungAuswertung.getWahlbeteiligungAuswertungTK(), emptyList()));
      }

      if (!zuLoeschen.isEmpty()) {
        for (WahlbeteiligungAuswertung wahlbeteiligungAuswertung : zuLoeschen) {
          wahlbeteiligungAuswertungDAO.remove(wahlbeteiligungAuswertung);
        }
        wahlDAO.flush(); // ELECT-7791
      }
      for (WahlbeteiligungAuswertung wahlbeteiligungAuswertung : wahlbeteiligungAuswertungs) {
        wahlbeteiligungAuswertungCreate.save(wahlbeteiligungAuswertung);
      }
    }

  }

  private List<Ergebniseingang> sammleErgebniseingange(WahlImportObject wahlImportObject) {

    List<Ergebniseingang> result = new ArrayList<>();

    for (Gebiet gebiet : wahlImportObject.getWahldaten().getGebiete()) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity();
      Object o = wahldurchfuehrungsgebiet.getAdditionalData(ERGEBNISEINGAENGE_KEY);
      if (o != null) {
        List<Ergebniseingang> eingaengeDesGebiets = (List<Ergebniseingang>) o;
        for (Ergebniseingang ergebniseingang : eingaengeDesGebiets) {
          ergebniseingang.setKeineReplikation(true); // ELECT-8428 Wahl mit Stimmeingaben kann nicht repliziert werden
        }
        result.addAll(eingaengeDesGebiets);
      }

    }

    return result;
  }

  @Override
  protected void updateGebietsart(Gebiet gebiet, Mandant mandant, Map<String, Gebietsart> gebietsartNamePersistenteGebietsartMap, Map<String, Wahlart> wahlartNamePersistenteWahlartMap) {
    super.updateGebietsart(gebiet, mandant, gebietsartNamePersistenteGebietsartMap, wahlartNamePersistenteWahlartMap);

    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = gebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity();

    if (wahldurchfuehrungsgebiet.getWahlartBeiAufteilung() != null) {
      String wahlartNameBeiAufteilung = wahldurchfuehrungsgebiet.getWahlartBeiAufteilung().getName();
      wahldurchfuehrungsgebiet.setWahlartBeiAufteilung(wahlartNamePersistenteWahlartMap.computeIfAbsent(wahlartNameBeiAufteilung, x -> {

        if (StringUtils.isBlank(wahlartNameBeiAufteilung)) {
          return null;
        }
        try {
          return wahlartDAO.findByUK(new WahlartUK(wahlartNameBeiAufteilung, mandant.getUk()));
        } catch (NoResultException nre) {
          return null;
        }
      }));

    }

  }
}
