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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangDAO;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeErfassenDescriptionResolver;
import de.ivu.elect.business.stimmeingabe.control.ErgebniseingangByEingangssequenznummerComparator;
import de.ivu.elect.business.stimmeingabe.control.ErgebniseingangEingangssequenznummerGenerator;
import de.ivu.elect.business.stimmeingabe.control.ErgebniseingangKopieren;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeCreate;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabePlausiBuilder;
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.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsherkunft;
import de.ivu.elect.business.wahl.entity.WahlErgebniseingangUebernahmeArt;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;

@Stateless
public class WahlErgebniseingangUebernahme {

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

  @Inject
  private transient ErgebnissequenznummerSetzenEvent ergebnissequenznummerSetzenEvent;

  @Inject
  private transient ErgebniseingangDAO ergebniseingangDAO;

  @Inject
  private transient ErgebniseingangKopieren ergebniseingangKopieren;

  @Inject
  private transient ErgebniseingangEingangssequenznummerGenerator ergebniseingangEingangssequenznummerGenerator;

  @Inject
  private transient StimmeingabeCreate stimmeingabeCreate;

  @Inject
  private transient StimmeingabeErfassenDescriptionResolver stimmeingabeErfassenDescriptionResolver;

  public WahlErgebniseingangUebernahme() {
    super();
  }

  public void tryCopyVorlaeufigeErgebniseingaenge(Wahldurchfuehrung wahldurchfuehrung, WahlErgebniseingangUebernahmeArt wahlErgebniseingangUebernahmeArt) {
    if (wahlErgebniseingangUebernahmeArt.isAutomatischeErgebniseingangUebernahmeVonVorlaeufigNachEndgueltigMoeglich()) {
      List<Ergebniseingang> ergebniseingaenge = findErgebniseingaenge(wahldurchfuehrung);

      Map<Wahldurchfuehrungsgebiet, List<Ergebniseingang>> vorlaeufigeErgebniseingaengePerGebiet = groupErgebniseingaengePerGebiet(ergebniseingaenge, Ergebniseingangsart.VORLAEUFIG);
      Map<Wahldurchfuehrungsgebiet, List<Ergebniseingang>> endgueltigeErgebniseingaengePerGebiet = groupErgebniseingaengePerGebiet(ergebniseingaenge, Ergebniseingangsart.ENDGUELTIG);

      long uebernommeneErgebniseingaenge = 0;
      StimmeingabePlausiBuilder stimmeingabePlausiBuilder = StimmeingabePlausiBuilder.NO_OP_BUILDER;

      if (wahlErgebniseingangUebernahmeArt.isErgebniseingangsfehlerstatusPlausibilisieren()) {
        stimmeingabePlausiBuilder = bestimmePlausibuilder(wahldurchfuehrung);
      }

      for (Entry<Wahldurchfuehrungsgebiet, List<Ergebniseingang>> entry : vorlaeufigeErgebniseingaengePerGebiet.entrySet()) {
        Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = entry.getKey();

        if (!endgueltigeErgebniseingaengePerGebiet.containsKey(wahldurchfuehrungsgebiet)) {
          List<Ergebniseingang> vorlaeufigeErgebniseingaenge = entry.getValue();

          Ergebniseingang vorlaeufigerErgebniseingang = getAcceptableLetzterVorlaeufigerErgebniseingang(vorlaeufigeErgebniseingaenge);

          if (vorlaeufigerErgebniseingang != null) {
            saveCopyAsEndgueltigerErgebniseingang(wahldurchfuehrung, vorlaeufigerErgebniseingang, wahlErgebniseingangUebernahmeArt, stimmeingabePlausiBuilder);

            uebernommeneErgebniseingaenge++;
          }
        }
      }

      LOG.info("Ergebniseingänge übernommen in {} von {} Gebieten der Wahl {}", uebernommeneErgebniseingaenge, vorlaeufigeErgebniseingaengePerGebiet.keySet().size(), wahldurchfuehrung);

      startErgebnisSequenznummerSetzen();
    } else {
      LOG.info("Übernahme nicht moeglich: " + wahlErgebniseingangUebernahmeArt);
    }
  }

  private StimmeingabePlausiBuilder bestimmePlausibuilder(Wahldurchfuehrung wahldurchfuehrung) {
    return stimmeingabeErfassenDescriptionResolver.getStimmeingabeErfassenDescriptionByWahl(wahldurchfuehrung).getStimmeingabePlausiBuilder();
  }

  private List<Ergebniseingang> findErgebniseingaenge(Wahldurchfuehrung wahldurchfuehrung) {
    return ergebniseingangDAO.findByWahl(wahldurchfuehrung);
  }

  private static Map<Wahldurchfuehrungsgebiet, List<Ergebniseingang>> groupErgebniseingaengePerGebiet(List<Ergebniseingang> ergebniseingaenge, Ergebniseingangsart ergebniseingangsart) {
    Map<Wahldurchfuehrungsgebiet, List<Ergebniseingang>> ergebniseingaengePerGebiet = new HashMap<>();

    for (Ergebniseingang ergebniseingang : ergebniseingaenge) {
      if (ergebniseingangsart.equals(ergebniseingang.getErgebniseingangsart())) {
        Wahldurchfuehrungsgebiet gebiet = ergebniseingang.getWahldurchfuehrungsgebiet();

        if (!ergebniseingaengePerGebiet.containsKey(gebiet)) {
          ergebniseingaengePerGebiet.put(gebiet, new ArrayList<>());
        }

        ergebniseingaengePerGebiet.get(gebiet).add(ergebniseingang);
      }
    }

    return ergebniseingaengePerGebiet;
  }

  protected Ergebniseingang getAcceptableLetzterVorlaeufigerErgebniseingang(List<Ergebniseingang> ergebniseingaenge) {
    sortByEingangssequenznummerDesc(ergebniseingaenge);

    return ergebniseingaenge.stream().filter(ergebniseingang -> ergebniseingang.isInErgebnisUebernehmbar() && !ergebniseingang.isFiktiv()).findFirst().orElse(null);
  }

  protected static void sortByEingangssequenznummerDesc(List<Ergebniseingang> ergebniseingaenge) {
    ergebniseingaenge.sort(ErgebniseingangByEingangssequenznummerComparator.DESCENDING);
  }

  private void saveCopyAsEndgueltigerErgebniseingang(Wahldurchfuehrung wahldurchfuehrung, Ergebniseingang template, WahlErgebniseingangUebernahmeArt wahlErgebniseingangUebernahmeArt,
      StimmeingabePlausiBuilder stimmeingabePlausiBuilder) {
    boolean stimmeingabenZuPlausibilisieren = wahlErgebniseingangUebernahmeArt.isErgebniseingangsfehlerstatusPlausibilisieren();

    Ergebniseingang ergebniseingangEndgueltig = createEndgueltigerErgebniseingang(template, stimmeingabenZuPlausibilisieren);

    if (stimmeingabenZuPlausibilisieren) {
      stimmeingabeCreate.saveOhneLetzterErgebniseingangInGebietPruefungMitStimmeingabePlausibilisierung(ergebniseingangEndgueltig, null, stimmeingabePlausiBuilder);
    } else {
      stimmeingabeCreate.saveOhneLetzterErgebniseingangInGebietPruefungOhneStimmeingabePlausibilisierung(ergebniseingangEndgueltig, null, stimmeingabePlausiBuilder);
    }

    if (wahlErgebniseingangUebernahmeArt.isErgebniseingangsfehlerstatusAnpassenAufMussFehlerSetzen()) {
      ergebniseingangEndgueltig.setUngueltigMussFehler();
    }
  }

  private Ergebniseingang createEndgueltigerErgebniseingang(Ergebniseingang template, boolean stimmeingabenZuPlausibilisieren) {
    ErgebniseingangErfasser erfasser = ErgebniseingangErfasser.benutzerOrDefault(); // ELECTSL-55

    return ergebniseingangKopieren.kopiereErgebniseingang(erfasser, ergebniseingangEingangssequenznummerGenerator.getNaechsteEingangssequenznummer(), Ergebniseingangsart.ENDGUELTIG,
        template.getErgebniseingabestufe(), Ergebniseingangsherkunft.UEBERNOMMENE_VORLAEUFIGE_WERTE, template, stimmeingabenZuPlausibilisieren);
  }

  private void startErgebnisSequenznummerSetzen() {
    ergebnissequenznummerSetzenEvent.sendChangeEvent();
  }
}
