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

import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;

import java.io.Serializable;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

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

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

import de.ivu.elect.business.ergebnis.boundary.ErgebnisDAO;
import de.ivu.elect.business.ergebnis.boundary.Ergebnisberechnung;
import de.ivu.elect.business.ergebnis.entity.AbstractErgebnis;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.ErgebnisPK;
import de.ivu.elect.business.ergebnis.entity.ErgebnisStatus;
import de.ivu.elect.business.ergebnis.entity.ErgebnisberechnungStatus;
import de.ivu.elect.business.mandant.boundary.MandantDAO;
import de.ivu.elect.business.mandant.entity.MandantPK;
import de.ivu.elect.business.sitzberechnung.control.SitzberechnungsalgorithmusFinder;
import de.ivu.elect.business.sitzberechnung.entity.SitzberechnungsalgorithmusKonfiguration;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.wahl.boundary.WahldurchfuehrungDAO;
import de.ivu.elect.business.wahl.entity.WahlParameterLoader;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.jee.security.AbstractPrincipal;

/**
 * siehe ELECT-9427
 */
public class ZwischenergebnisberechnungHelper implements Serializable {

  private static final long serialVersionUID = 1L;

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

  @Inject
  private transient MandantDAO mandantDAO;

  @Inject
  private transient WahldurchfuehrungDAO wahldurchfuehrungDAO;

  @Inject
  private transient WahlParameterLoader wahlParameterLoader;

  @Inject
  private transient ErgebnisDAO ergebnisDAO;

  @Inject
  private transient StimmeingabeErgebnisuebernahme stimmeingabeErgebnisuebernahme;

  @Inject
  private transient Ergebnisberechnung ergebnisberechnung;

  @Inject
  private transient SitzberechnungsalgorithmusFinder sitzberechnungsalgorithmusFinder;

  public boolean isErgebnisZuBerechnen(MandantPK mandantPK) {
    return getZuBerechnendesErgebnis(mandantPK) != null;
  }

  @Nullable
  public ErgebnisPK getZuBerechnendesErgebnis(MandantPK mandantPK) {
    List<Wahldurchfuehrung> alleWahlen = new ArrayList<>(wahldurchfuehrungDAO.findByAll(mandantDAO.findByPrimaryKey(mandantPK)));
    Collections.shuffle(alleWahlen); // zufällige Reihenfolge
    // nur ein Ergebnis pro Transaktion
    return alleWahlen.stream().map(this::bestimmeZuBerechnendesErgebnis).filter(Objects::nonNull).findFirst().orElse(null);
  }

  @Nullable
  private ErgebnisPK bestimmeZuBerechnendesErgebnis(Wahldurchfuehrung wahldurchfuehrung) {
    ErgebnisberechnungStatus ergebnisberechnungStatus = wahlParameterLoader.resolveEnumValue(wahldurchfuehrung, WahlParameterWerte.ERGEBNISBERECHNUNG_STATUS, ErgebnisberechnungStatus.class);
    if (ergebnisberechnungStatus == null /* ELECT-8474 */ || ergebnisberechnungStatus.isAktiv()) {
      List<Ergebnis> ergebnisse = new ArrayList<>(ergebnisDAO.findByWahl(wahldurchfuehrung));
      Collections.shuffle(ergebnisse); // zufällige Reihenfolge
      for (Ergebnis ergebnis : ergebnisse) {
        if (ergebnis.getErgebnisart().isVorlaeufig() || ergebnis.getErgebnisart().isEndgueltig()) {
          if (isBerechnungAutomatischErzwungen(ergebnis)) {
            return ergebnis.getPk();
          }

          Long maxEingangssequenznummer = stimmeingabeErgebnisuebernahme.bestimmeMaxErgebnissequenznummer(wahldurchfuehrung,
              Ergebniseingangsart.getErgebniseingangsartFuerErgebnisArt(ergebnis.getErgebnisart()));

          // nur wenn Eingänge vorhanden ab hier überhaupt sinnvoll
          if (maxEingangssequenznummer != null) {

            if (isNull(ergebnis.getEingangssequenznummer())) {
              return ergebnis.getPk();
            }

            if (ErgebnisStatus.ZU_BERECHNEN.equals(ergebnis.getErgebnisStatus())) {
              return ergebnis.getPk();
            }

            // neue Eingänge vorhanden?
            if (maxEingangssequenznummer > ergebnis.getEingangssequenznummer()) {
              return ergebnis.getPk();
            }
          }
        }
      }
    } else {
      LOG.info("Ergebnisberechnung für Wahl deaktiviert: " + wahldurchfuehrung);
    }
    return null;
  }

  // NOTE ELECT-3967, dass bspw. nach dem (manuellen) Löschen eines Ergebniseinganges, die Ergebnisberechnung (automatisch) erzwungen werden muss!
  private boolean isBerechnungAutomatischErzwungen(Ergebnis ergebnis) {
    return nonNull(ergebnis.getEingangssequenznummer()) && AbstractErgebnis.EINGANGSSEQUENZNUMMER_BERECHNUNG_UND_UEBERNAHME_AUTOMATISCH_ERZWUNGEN == ergebnis.getEingangssequenznummer();
  }

  public ZwischenergebnisberechnungStatus createZwischenergebnisberechnungStatus(ErgebnisPK ergebnisPK, boolean loeseKonflikte, Date date) {
    Ergebnis ergebnis = ergebnisDAO.findByPrimaryKey(ergebnisPK);
    return new ZwischenergebnisberechnungStatus(ergebnis.getErgebnisart(), ergebnis.getWahl().getName(), date, loeseKonflikte);
  }

  public void ergebnisUebernehmen(ErgebnisPK ergebnisPK) {
    Ergebnis ergebnis = ergebnisDAO.findByPrimaryKey(ergebnisPK);
    stimmeingabeErgebnisuebernahme.ergebnisUebernehmen(ergebnis);
  }

  public void berechneErgebnis(ErgebnisPK ergebnisPK, boolean loeseKonflikte, Date date) {
    Ergebnis ergebnis = ergebnisDAO.findByPrimaryKey(ergebnisPK);
    Wahldurchfuehrung wahldurchfuehrung = ergebnis.getWahl().castTo(Wahldurchfuehrung.class).getEntity();
    LOG.info("Berechne Ergebnis: " + ergebnis + " für Wahl: " + wahldurchfuehrung + ", pk = " + ergebnis.getPk());
    SitzberechnungsalgorithmusKonfiguration konfiguration = sitzberechnungsalgorithmusFinder.getSitzberechnungsalgorithmus(wahldurchfuehrung).getDefaultKonfiguration();
    konfiguration.setKonflikeSelbstLoesen(loeseKonflikte);
    ergebnis.setName(DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.GERMANY).format(date));
    ergebnisberechnung.aktualisiereErgebnis(ergebnisPK, AbstractPrincipal.SYSTEM_BENUTZERKENNUNG, konfiguration);
    ergebnis.setZeitstempel(date);
  }

  public void flushClear() {
    LOG.info("Flush");
    ergebnisDAO.flush();
    LOG.info("Clear");
    ergebnisDAO.clear();
    LOG.info("done");
  }
}
