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

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 java.util.stream.Collectors;

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.AWerteAktualisierer;
import de.ivu.elect.business.gebietsbaum.control.AnzahlWahlberechtigteImportExchange;
import de.ivu.elect.business.gebietsbaum.entity.AbstractGebiet;
import de.ivu.elect.business.gebietsbaum.entity.AnzahlWahlberechtigteImportContext;
import de.ivu.elect.business.gebietsbaum.entity.AnzahlWahlberechtigteExchangeObjekt;
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;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;


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<AnzahlWahlberechtigteExchangeObjekt, 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 WasGebietEditHelper wasGebietEditHelper;

  @Inject
  private transient AWerteReset aWerteReset;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  public AnzahlWahlberechtigteImporter() {
    super();
  }

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

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

    Wahldurchfuehrungsgebiet uebergeordnetesGebiet = bestimmeUebergeordnetesGebiet(context);

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

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

  @Nullable
  private Wahldurchfuehrungsgebiet bestimmeUebergeordnetesGebiet(AnzahlWahlberechtigteImportContext context) {
    if (context.getUebergeordnetesGebietPK() == null) {
      return null;
    }
    return wahldurchfuehrungsgebietDAO.findByPrimaryKey(context.getUebergeordnetesGebietPK());

  }

  @Override
  protected void erstelleBericht(AnzahlWahlberechtigteExchangeObjekt importObject, AnzahlWahlberechtigteImportContext context, ImportOptions importOptions, StringBuilder bericht, boolean vorab) {
    // nichts zu tun
  }

  @Override
  protected ImportUpdateReport createImportUpdateReport(AnzahlWahlberechtigteExchangeObjekt importObject, Violations violations, AnzahlWahlberechtigteImportContext context, ImportOptions importOptions,
                                                        StringBuilder bericht) {
    int nurInDatei = 0;
    int nurInDatenbank;
    int inDateiInDatenbank = 0;

    Wahl wahl = wahlDAO.attach(context.getWahl());
    Map<GebietTK, Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebietInDbPerGebietTk = findWahldurchfuehrungsgebietInDbPerGebietTk(wahl);

    HashSet<Wahldurchfuehrungsgebiet> alleErfassungsgebiete = (HashSet<Wahldurchfuehrungsgebiet>) wahldurchfuehrungsgebietInDbPerGebietTk.values().stream()
        .filter(
            AbstractGebiet::isErfassungsgebietLautGebietsart).collect(Collectors.toSet());
    Map<GebietTK, List<Wahldurchfuehrungsgebiet>> wahldurchfuehrungsgebietInDbPerGebietTkNormalized = findWahldurchfuehrungsgebietInDbPerGebietTkNormalized(
        wahl);
    Wahldurchfuehrungsgebiet uebergeordnetesGebiet = bestimmeUebergeordnetesGebiet(context);

    Set<Wahldurchfuehrungsgebiet> gebietInDateiGefunden = new HashSet<>();
    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDatei : importObject.getGebiete()) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb = findeGebiet(wahldurchfuehrungsgebietInDatei, wahldurchfuehrungsgebietInDbPerGebietTk,
          wahldurchfuehrungsgebietInDbPerGebietTkNormalized, uebergeordnetesGebiet);
      boolean aktualisiert = false;
      if (wahldurchfuehrungsgebietInDb != null) { // immer wahr, da vorher validiert
        gebietInDateiGefunden.add(wahldurchfuehrungsgebietInDb);
        if (aWerteReset.isAktualisiereBestehendenDatenZulaessig(wahldurchfuehrungsgebietInDb, context.isErfassteGebietUeberschreiben())) {
          if (isGebietUpdateZulaessig(importOptions, wahldurchfuehrungsgebietInDb)) {
            aktualisiert = true;
          }
        }
      }
      if (aktualisiert) {
        inDateiInDatenbank++;
      } else {
        nurInDatei++;
      }
    }

    nurInDatenbank = (int) alleErfassungsgebiete.stream()
        .filter(wahldurchfuehrungsgebiet -> wahldurchfuehrungsgebiet.getAnzahlWahlberechtigterGesamtA1A2A3() != null)
        .filter(wahldurchfuehrungsgebiet -> !gebietInDateiGefunden.contains(wahldurchfuehrungsgebiet)).count();

    return new ImportUpdateReport(violations, nurInDatei, nurInDatenbank, inDateiInDatenbank, importOptions, bericht);

  }

  @Override
  protected void importiereImportObject(AnzahlWahlberechtigteExchangeObjekt importObjekt, AnzahlWahlberechtigteImportContext context, ImportOptions importOptions, DateiImportBerichtCustomData customDateiImportBericht, StringBuilder bericht) {

    Wahl wahl = wahlDAO.attach(context.getWahl());

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

    HashSet<Wahldurchfuehrungsgebiet> alleErfassungsgebiete = (HashSet<Wahldurchfuehrungsgebiet>) wahldurchfuehrungsgebietInDbPerGebietTk.values().stream()
        .filter(
            AbstractGebiet::isErfassungsgebietLautGebietsart).collect(Collectors.toSet());

    Map<GebietTK, List<Wahldurchfuehrungsgebiet>> wahldurchfuehrungsgebietInDbPerGebietTkNormalized = findWahldurchfuehrungsgebietInDbPerGebietTkNormalized(
        wahl);
    Wahldurchfuehrungsgebiet uebergeordnetesGebiet = bestimmeUebergeordnetesGebiet(context);

    Set<Wahldurchfuehrungsgebiet> gebietInDateiGefunden = new HashSet<>();

    int anzahlAktualsisiert = 0;
    int anzahlGeloescht = 0;

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDatei : importObjekt.getGebiete()) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb = findeGebiet(wahldurchfuehrungsgebietInDatei, wahldurchfuehrungsgebietInDbPerGebietTk,
          wahldurchfuehrungsgebietInDbPerGebietTkNormalized, uebergeordnetesGebiet);
      if (wahldurchfuehrungsgebietInDb != null) { // immer wahr, da vorher validiert
        gebietInDateiGefunden.add(wahldurchfuehrungsgebietInDb);
        if (aWerteReset.isAktualisiereBestehendenDatenZulaessig(wahldurchfuehrungsgebietInDb, context.isErfassteGebietUeberschreiben())) {
          if (isGebietUpdateZulaessig(importOptions, wahldurchfuehrungsgebietInDb)) {
            anzahlAktualsisiert++;
            wahldurchfuehrungsgebietInDb.setAnzahlWahlberechtigteOhneWahlschein(wahldurchfuehrungsgebietInDatei.getAnzahlWahlberechtigteOhneWahlschein());
            wahldurchfuehrungsgebietInDb.setAnzahlWahlberechtigteMitWahlschein(wahldurchfuehrungsgebietInDatei.getAnzahlWahlberechtigteMitWahlschein());
            wahldurchfuehrungsgebietInDb.setAnzahlWahlberechtigteNichtImWaehlerverzeichnis(
                wahldurchfuehrungsgebietInDatei.getAnzahlWahlberechtigteNichtImWaehlerverzeichnis());
          }
        }
      }
    }

    if (importOptions.isLoescheBestehendenDatenWennNichtInImportDatenVorhanden()) {
      for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : alleErfassungsgebiete) {
        if (!gebietInDateiGefunden.contains(wahldurchfuehrungsgebiet)) {
          if (aWerteReset.isAktualisiereBestehendenDatenZulaessig(wahldurchfuehrungsgebiet, context.isErfassteGebietUeberschreiben())) {
            anzahlGeloescht++;
            wahldurchfuehrungsgebiet.resetAWerte();
          }
        }
      }
    }

    bericht.append(getMessage("anzahl.wahlberechtigte.anzahl.gebiete.aktualsiert", anzahlAktualsisiert));
    bericht.append("\n");
    bericht.append(getMessage("anzahl.wahlberechtigte.anzahl.gebiete.geloescht", anzahlGeloescht));

    AWerteAktualisierer aWerteAktualisierer = new AWerteAktualisierer(wahl, wahldurchfuehrungsgebietDAO, wasGebietEditHelper.getUmgangHinterlegteAWerte(wahl));

    for (Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet : alleErfassungsgebiete) {
      aWerteAktualisierer.aktualisiereAWerteFuerUebergeordneteGebieteVon(wahldurchfuehrungsgebiet);
    }

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

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

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

  private boolean isGebietUpdateZulaessig(ImportOptions importOptions, Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietInDb) {
    return importOptions.isAktualisiereBestehendenDaten() || importOptions.isImportDatenUebernehmenWennNichtBereitsVorhanden() && wahldurchfuehrungsgebietInDb.getAnzahlWahlberechtigterGesamtA1A2A3() == null;
  }

  @Override
  protected ImportExchange<AnzahlWahlberechtigteExchangeObjekt, 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, @Nullable Wahldurchfuehrungsgebiet uebergeordnetesGebiet) {
    // ELECT-12797
    List<Wahldurchfuehrungsgebiet> candidates = wahldurchfuehrungsgebietInDbPerGebietTkNormalized.getOrDefault(
        createGebietTKNormalized(wahldurchfuehrungsgebietInDatei), Collections.emptyList());

    if (uebergeordnetesGebiet != null) {
      // ELECT-12905
      candidates = candidates.stream().filter(uebergeordnetesGebiet::isInGebietshierarchieUebergeordnetesGebietVon).collect(Collectors.toList());
    }

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


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

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

  @Override
  protected void anlegenWennNichtVorhanden(AnzahlWahlberechtigteExchangeObjekt importObjekt, AnzahlWahlberechtigteImportContext context, DateiImportBerichtCustomData customDateiImportBericht,
                                           StringBuilder bericht) {
    throw new UnsupportedOperationException("importiereImportObject");
  }

  @Override
  protected void loescheBestehendenDatenWennNichtInImportDatenVorhanden(AnzahlWahlberechtigteExchangeObjekt importObjekt, AnzahlWahlberechtigteImportContext context,
                                                                        DateiImportBerichtCustomData customDateiImportBericht, StringBuilder bericht) {
    throw new UnsupportedOperationException("importiereImportObject");
  }

  @Override
  protected void aktualisiereBestehendenDaten(AnzahlWahlberechtigteExchangeObjekt importObjekt, AnzahlWahlberechtigteImportContext context, DateiImportBerichtCustomData customDateiImportBericht,
                                              StringBuilder bericht) {
    throw new UnsupportedOperationException("importiereImportObject");
  }
}
