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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

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

import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.exchange.control.AbstractImporter;
import de.ivu.elect.business.exchange.control.ImportExchange;
import de.ivu.elect.business.exchange.entity.ImportUpdateReport;
import de.ivu.elect.business.gebietsbaum.control.AWerte;
import de.ivu.elect.business.gebietsbaum.control.AnzahlWahlberechtigteImportExchange;
import de.ivu.elect.business.gebietsbaum.entity.AnzahlWahlberechtigteImportContext;
import de.ivu.elect.business.gebietsbaum.entity.AnzahlWahlberechtigteImportObjekt;
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.WahldurchfuehrungsgebietMeta;
import de.ivu.elect.business.replikation.boundary.ReplikationErzeugen;
import de.ivu.elect.business.stimmeingabe.boundary.LetzterEingangLoader;
import de.ivu.elect.business.wahl.boundary.WahlDAO;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.jee.entity.Meta;
import de.ivu.elect.jee.exchange.ImportOptions;
import de.ivu.elect.jee.exchange.Violations;


import static de.ivu.elect.business.gebietsbaum.control.EntityMitWurzelgebietDAO.fromDao;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.leftPad;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

@Stateless
public class AnzahlWahlberechtigteImporter extends AbstractImporter<AnzahlWahlberechtigteImportObjekt, AnzahlWahlberechtigteImportContext, DateiImportBerichtCustomData> {

  @Inject
  private transient WahlDAO wahlDAO;

  @Inject
  private transient GebietDAO gebietDAO;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  private transient WahldurchfuehrungsgebietMeta wahldurchfuehrungsgebietMeta;

  @Inject
  private transient AnzahlWahlberechtigteImportExchange anzahlWahlberechtigteImportExchange;

  @Inject
  private transient ReplikationErzeugen replikationErzeugen;

  @Inject
  private transient GebietsbaumImporterReportUtil gebietsbaumImporterReportUtil;

  @Inject
  private transient LetzterEingangLoader letzterEingangLoader;

  public AnzahlWahlberechtigteImporter() {
    super();
  }

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

  @Override
  protected void validiere(Violations violations, AnzahlWahlberechtigteImportObjekt importObjekt, AnzahlWahlberechtigteImportContext context, ImportOptions importOptions) {
    Map<GebietTK, Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietInDbPerGebietTk = findWahldurchfuehrungsgebietInDbPerGebietTk(
        wahlDAO.attach(context.getWahl()));
    Map<GebietTK, List<Wahldurchfuehrungsgebiet>> wahldurchfuehrungsgebietInDbPerGebietTkNormalized = findWahldurchfuehrungsgebietInDbPerGebietTkNormalized(
        wahlDAO.attach(context.getWahl()));

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDatei : importObjekt.getGebiete()) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb = findeGebiet(wahldurchfuehrungsgebietInDatei, wahldurchfuehrungsgebietInDbPerGebietTk,
          wahldurchfuehrungsgebietInDbPerGebietTkNormalized);

      if (wahldurchfuehrungsgebietInDb == null) {
        violations.addError("anzahl.wahlberechtigte.import.gebiet.in.db.nicht.vorhanden", wahldurchfuehrungsgebietInDatei.getGebietTK());
      } else {
        if (!isAktualisiereBestehendenDatenZulaessig(wahldurchfuehrungsgebietInDb)) {
          violations.addWarning("anzahl.wahlberechtigte.import.gebiet.in.db.gesperrt.by.ergebniseingang", wahldurchfuehrungsgebietInDatei.getGebietTK());
        }
      }
    }
  }


  @Override
  protected void erstelleBericht(AnzahlWahlberechtigteImportObjekt importObject, AnzahlWahlberechtigteImportContext context, ImportOptions importOptions, StringBuilder bericht, boolean vorab) {
    gebietsbaumImporterReportUtil.erstelleBericht(context.getMandant(), new ArrayList<>(importObject.getGebiete()), bericht);
  }

  @Override
  protected ImportUpdateReport createImportUpdateReport(AnzahlWahlberechtigteImportObjekt importObject, Violations violations, AnzahlWahlberechtigteImportContext context, ImportOptions importOptions,
      StringBuilder bericht) {
    return gebietsbaumImporterReportUtil.createImportUpdateReport(findGebieteInDb(context), new ArrayList<>(importObject.getGebiete()), this::createGebietTKNormalized, violations, importOptions,
        bericht);
  }

  @Override
  protected void anlegenWennNichtVorhanden(AnzahlWahlberechtigteImportObjekt importObjekt, AnzahlWahlberechtigteImportContext context, DateiImportBerichtCustomData customDateiImportBericht,
      StringBuilder bericht) {
    throw new UnsupportedOperationException("Anlegen, wenn nicht vorhanden nicht unterstützt");
  }

  @Override
  protected void aktualisiereBestehendenDaten(AnzahlWahlberechtigteImportObjekt importObjekt, AnzahlWahlberechtigteImportContext context, DateiImportBerichtCustomData customDateiImportBericht,
      StringBuilder bericht) {
    Wahl wahl = wahlDAO.attach(context.getWahl());

    Map<GebietTK, Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietInDbPerGebietTk = findWahldurchfuehrungsgebietInDbPerGebietTk(wahl);
    Map<GebietTK, List<Wahldurchfuehrungsgebiet>> wahldurchfuehrungsgebietInDbPerGebietTkNormalized = findWahldurchfuehrungsgebietInDbPerGebietTkNormalized(
        wahl);

    Set<GebietTK> gebietTksInDatei = new HashSet<>();

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDatei : importObjekt.getGebiete()) {

      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb = findeGebiet(wahldurchfuehrungsgebietInDatei, wahldurchfuehrungsgebietInDbPerGebietTk,
          wahldurchfuehrungsgebietInDbPerGebietTkNormalized);
      if (wahldurchfuehrungsgebietInDb != null) { // immer wahr, da vorher validiert
        if (isAktualisiereBestehendenDatenZulaessig(wahldurchfuehrungsgebietInDb)) {
          wahldurchfuehrungsgebietInDb.setAnzahlWahlberechtigteOhneWahlschein(wahldurchfuehrungsgebietInDatei.getAnzahlWahlberechtigteOhneWahlschein());
          wahldurchfuehrungsgebietInDb.setAnzahlWahlberechtigteMitWahlschein(wahldurchfuehrungsgebietInDatei.getAnzahlWahlberechtigteMitWahlschein());
          wahldurchfuehrungsgebietInDb.setAnzahlWahlberechtigteNichtImWaehlerverzeichnis(
              wahldurchfuehrungsgebietInDatei.getAnzahlWahlberechtigteNichtImWaehlerverzeichnis());
        }

        gebietTksInDatei.add(wahldurchfuehrungsgebietInDb.getGebietTK());
      }
    }

    // Kumulieren...
    kumuliereAWerte(wahl.getWurzelgebiet(fromDao(gebietDAO)), gebietTksInDatei);

    // ... und Replizieren
    if (replikationErzeugen.isReplikationNotwendig()) {
      wahldurchfuehrungsgebietDAO.flush();

      replikationErzeugen.replikationGebietUpdate(wahldurchfuehrungsgebietInDbPerGebietTk.values());
    }
  }

  private boolean isAktualisiereBestehendenDatenZulaessig(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return wahldurchfuehrungsgebiet != null && wahldurchfuehrungsgebiet.isErfassungsgebiet() && !letzterEingangLoader.hasAnyErgebniseingang(wahldurchfuehrungsgebiet.getWahl().getPk(), wahldurchfuehrungsgebiet.getPk());
  }

  private AWerte kumuliereAWerte(Gebiet gebiet, Set<GebietTK> gebietTKsInDatei) {
    AWerte result = new AWerte();

    boolean aWerteInDateiVorhanden = gebietTKsInDatei.contains(gebiet.getGebietTK());
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = wahldurchfuehrungsgebietDAO.findByPrimaryKey(gebiet.getPk());

    // kumulieren aus Untergebieten
    for (Gebiet untergebiet : gebiet.getUntergeordneteGebiete()) {
      result.add(kumuliereAWerte(untergebiet, gebietTKsInDatei));
    }

    if (aWerteInDateiVorhanden) {
      // berechnete Werte wegwerfen und aus Datei nehmen (aus Gebiet)
      result = new AWerte();
      result.add(wahldurchfuehrungsgebiet);
    } else {
      // Wenn berechnet => zurückschreiben
      result.schreibeWerte(wahldurchfuehrungsgebiet);
    }

    return result;
  }

  @Override
  protected void loescheBestehendenDatenWennNichtInImportDatenVorhanden(AnzahlWahlberechtigteImportObjekt importObjekt, AnzahlWahlberechtigteImportContext context,
      DateiImportBerichtCustomData customDateiImportBericht, StringBuilder bericht) {
    throw new UnsupportedOperationException("Löschen nicht unterstützt");
  }

  @Override
  protected ImportExchange<AnzahlWahlberechtigteImportObjekt, AnzahlWahlberechtigteImportContext> getExchange() {
    return anzahlWahlberechtigteImportExchange;
  }

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

  private Map<GebietTK, Wahldurchfuehrungsgebiet> findWahldurchfuehrungsgebietInDbPerGebietTk(Wahl wahl) {
    return wahldurchfuehrungsgebietDAO.findByWahl(wahl).stream() //
        .collect(toMap(Gebiet::getGebietTK, Function.identity()));

  }

  private Map<GebietTK, List<Wahldurchfuehrungsgebiet>> findWahldurchfuehrungsgebietInDbPerGebietTkNormalized(Wahl wahl) {
    return wahldurchfuehrungsgebietDAO.findByWahl(wahl).stream() //
        .filter(wahldurchfuehrungsgebiet -> isNotBlank(wahldurchfuehrungsgebiet.getNummerBezirkFallsGesetzt())) //
        .collect(groupingBy(this::createGebietTKNormalized));
  }

  @Nullable
  private Wahldurchfuehrungsgebiet findeGebiet(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDatei, Map<GebietTK, Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietInDbPerGebietTk, Map<GebietTK, List<Wahldurchfuehrungsgebiet>> wahldurchfuehrungsgebietInDbPerGebietTkNormalized) {
    // ELECT-12797
    List<Wahldurchfuehrungsgebiet> candidates = wahldurchfuehrungsgebietInDbPerGebietTkNormalized.getOrDefault(
        createGebietTKNormalized(wahldurchfuehrungsgebietInDatei), Collections.emptyList());
    if (candidates.size() == 1) {
      return candidates.iterator().next();
    }

    return wahldurchfuehrungsgebietInDbPerGebietTk.get(wahldurchfuehrungsgebietInDatei.getGebietTK());
  }

  private GebietTK createGebietTKNormalized(Gebiet gebiet) {
    String nummerNormalized = trimToEmpty(gebiet.getNummerBezirkFallsGesetzt());
    nummerNormalized = leftPad(nummerNormalized, 20, "0");

    return new GebietTK(nummerNormalized, gebiet.getGebietsart().getGebietsartTK());
  }

  private List<Gebiet> findGebieteInDb(AnzahlWahlberechtigteImportContext context) {
    return gebietDAO.findByWahl(context.getWahl());
  }
}
