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

import static de.ivu.elect.business.gebietsbaum.control.EntityMitWurzelgebietDAO.fromDao;
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;

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

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.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;

@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;

  public AnzahlWahlberechtigteImporter() {
    super();
  }

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

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

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDatei : importObjekt.getGebiete()) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb = wahldurchfuehrungsgebietInDbPerGebietTkNormalized.get(createGebietTKNormalized(wahldurchfuehrungsgebietInDatei));

      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> wahldurchfuehrungsgebietInDbPerGebietTkNormalized = findWahldurchfuehrungsgebietInDbPerGebietTkNormalized(wahl);
    Set<GebietTK> gebietTksInDatei = new HashSet<>();

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDatei : importObjekt.getGebiete()) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb = wahldurchfuehrungsgebietInDbPerGebietTkNormalized.get(createGebietTKNormalized(wahldurchfuehrungsgebietInDatei));

      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(wahldurchfuehrungsgebietInDbPerGebietTkNormalized.values());
    }
  }

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

  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> findWahldurchfuehrungsgebietInDbPerGebietTkNormalized(Wahl wahl) {
    return wahldurchfuehrungsgebietDAO.findByWahl(wahl).stream() //
        .filter(wahldurchfuehrungsgebiet -> isNotBlank(wahldurchfuehrungsgebiet.getNummerBezirkFallsGesetzt())) //
        .collect(toMap(this::createGebietTKNormalized, Function.identity()));
  }

  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());
  }
}
