
package de.ivu.elect.business.stimmeingabe.control;

import java.util.Arrays;
import java.util.Date;

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

import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabePlausiKonfigurationDAO;
import de.ivu.elect.business.stimmeingabe.control.validierung.ErgebniseingangValidator;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestufe;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsherkunft;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeHerkunftUmgang;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiErgebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiTyp;
import de.ivu.elect.business.wahl.entity.WahlParameterLoader;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.jee.exception.ElectCommonException;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.performance.PerformanceLog;
import de.ivu.elect.jee.performance.PerformanceLogTransactionHandling;
import de.ivu.elect.jee.performance.ViewType;

/**
 * Achtung, die Logik zum Persistieren der Eingänge befindet sich unglücklicher Weise in StimmeingabeCreate!!
 */
public class ErgebniseingangCreate extends AbstractErgebniseingangCreate {

  private static final String MESSAGE_KEY_KONKURRIERENDE_AENDERUNG = "stimmeingabe.erfassen.pruefen.eingabe.warnung.konkurrierende.aenderung";

  @Inject
  private transient Messages messages;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  private transient ErgebniseingangEingangssequenznummerGenerator ergebniseingangEingangssequenznummerGenerator;

  @Inject
  private transient ErgebniseingangEingabeHashCodeGenerator ergebniseingangEingabeHashCodeGenerator;

  @Inject
  private transient WahlParameterLoader wahlParameterLoader;

  @Inject
  private transient StimmeingabePlausiKonfigurationDAO stimmeingabePlausiKonfigurationDAO;

  public ErgebniseingangCreate() {
    super();
  }

  @PerformanceLog(viewType = ViewType.STIMMENEINGABE, transactionHandling = PerformanceLogTransactionHandling.IMMEDIATELY)
  public void save(Ergebniseingang ergebniseingang, @Nullable Ergebniseingang letzterErgebniseingangInGebietErwartet, boolean letztenErgebniseingangInGebietPruefen,
                   Iterable<ErgebniseingangValidator> ergebniseingangValidatoren) {
    var wahldurchfuehrungsgebiet = wahldurchfuehrungsgebietDAO.attach(ergebniseingang.getWahldurchfuehrungsgebiet());
    ergebniseingang.setWahldurchfuehrungsgebiet(wahldurchfuehrungsgebiet);

    var letzterErgebniseingangInGebiet = getLetztenErgebniseingangInGebiet(wahldurchfuehrungsgebiet, ergebniseingang.getErgebniseingangsart(),
        ergebniseingang.getErgebniseingabestufe());

    if (letztenErgebniseingangInGebietPruefen && isLetzterErgebniseingangInGebietErwartetVeraltet(letzterErgebniseingangInGebietErwartet,
        letzterErgebniseingangInGebiet)) {
      throw new ElectCommonException(MESSAGE_KEY_KONKURRIERENDE_AENDERUNG, letzterErgebniseingangInGebiet.getErfasser(),
          letzterErgebniseingangInGebiet.getZeitstempel(),
          messages.lokalisiere(letzterErgebniseingangInGebiet.getErgebniseingangsfehlerstatus()));
    }

    ergebniseingang.setEingangssequenznummer(ergebniseingangEingangssequenznummerGenerator.getNaechsteEingangssequenznummer());

    var zeitstempel = ergebniseingang.getZeitstempel();

    if (zeitstempel == null || zeitstempel.after(new Date())) {
      ergebniseingang.setZeitstempel(new Date());
    }

    ergebniseingang.setGueltigKeinFehler(); // HINT: Sollte spaeter gesetzt werden...

    if (isErgebniseingangFiktiv(letzterErgebniseingangInGebiet) && isErgebniseingangManuelleEingabe(ergebniseingang)) {
      // Herkunft "Fiktiv" auch bei manuellen Änderungen beibehalten
      ergebniseingang.setErgebniseingangsherkunft(Ergebniseingangsherkunft.FIKTIVE_WERTE);
    }

    validiereErgebniseingang(ergebniseingang, ergebniseingangValidatoren);

    if (isHashCodeBerechnenNotwendig(wahldurchfuehrungsgebiet, ergebniseingang)) {
      ergebniseingang.setEingabeHashCode(ergebniseingangEingabeHashCodeGenerator.calculateHashCode(wahldurchfuehrungsgebiet, ergebniseingang));
    }

    var ergebniseingabestatus = ergebniseingang.getWahldurchfuehrungsgebiet()
        .getErgebniseingabestatus(ergebniseingang.getErgebniseingangsart().korrespondierendeErgebnisart());

    if (ergebniseingabestatus.isEingabeMoeglichStatus() && ergebniseingang.getErgebniseingangsherkunft() != null) {
      var stimmeingabeHerkunftUmgang = wahlParameterLoader.resolveEnumValue(ergebniseingang.getWahldurchfuehrung(),
          WahlParameterWerte.STIMMEINGABE_HERKUNFT_SETZEN,
          StimmeingabeHerkunftUmgang.class);
      if (stimmeingabeHerkunftUmgang != null) {
        var ergebniseingangsherkunft = ergebniseingang.getErgebniseingangsherkunft();
        var folgeStatus = ergebniseingangsherkunft.getEingabeMoeglichStatus(ergebniseingabestatus, stimmeingabeHerkunftUmgang);
        if (!folgeStatus.equals(ergebniseingabestatus)) {
          ergebniseingang.getWahldurchfuehrungsgebiet()
              .setErgebniseingabestatus(folgeStatus, ergebniseingang.getErgebniseingangsart().korrespondierendeErgebnisart());
        }
      }
    }

    super.save(ergebniseingang);
  }

  private boolean isHashCodeBerechnenNotwendig(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingang ergebniseingang) {
    return Arrays.stream(StimmeingabePlausiTyp.values()).filter(StimmeingabePlausiTyp::isHashCodeRelevant).flatMap(value -> stimmeingabePlausiKonfigurationDAO
            .findByWahlAndErgebniseingangsartAndTyp(wahldurchfuehrungsgebiet.getWahl(),
                StimmeingabePlausiErgebniseingangsart.of(ergebniseingang.getErgebniseingangsart()), value).stream())
        .anyMatch(stimmeingabePlausiKonfiguration -> stimmeingabePlausiKonfiguration.getStatus().isPlausibilisiereStimmeingabe());
  }

  private Ergebniseingang getLetztenErgebniseingangInGebiet(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart, Ergebniseingabestufe ergebniseingabestufe) {
    return wahldurchfuehrungsgebiet.getLetzterErgebniseingangInErgebniseingabestufe(dao, ergebniseingangsart, ergebniseingabestufe);
  }

  private boolean isLetzterErgebniseingangInGebietErwartetVeraltet(@Nullable Ergebniseingang letzterErgebniseingangInGebietErwartet, Ergebniseingang letzterErgebniseingangInGebiet) {
    if (letzterErgebniseingangInGebietErwartet == null) {
      return letzterErgebniseingangInGebiet != null;
    }

    return letzterErgebniseingangInGebiet.getEingangssequenznummer() > letzterErgebniseingangInGebietErwartet.getEingangssequenznummer();
  }

  private boolean isErgebniseingangFiktiv(Ergebniseingang ergebniseingang) {
    return ergebniseingang != null && ergebniseingang.getErgebniseingangsherkunft() != null && ergebniseingang.getErgebniseingangsherkunft().isFiktiv();
  }

  private boolean isErgebniseingangManuelleEingabe(Ergebniseingang ergebniseingang) {
    return ergebniseingang != null && ergebniseingang.getErgebniseingangsherkunft() != null && ergebniseingang.getErgebniseingangsherkunft()
        .isBenutzerEingabe();
  }

  private void validiereErgebniseingang(Ergebniseingang ergebniseingang, Iterable<ErgebniseingangValidator> ergebniseingangValidatoren) {
    for (var ergebniseingangValidator : ergebniseingangValidatoren) {
      ergebniseingangValidator.validate(ergebniseingang);
    }
  }
}

