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

import java.util.Collection;
import java.util.EnumSet;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

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

import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
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.Ergebniseingangsfehlerstatus;
import de.ivu.elect.business.system.boundary.DbInfo;
import de.ivu.elect.business.wahl.entity.Wahl;

public class ErgebniseingangDAOHelper {

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

  private final AbstractErgebniseingangDAO dao;

  public ErgebniseingangDAOHelper(AbstractErgebniseingangDAO dao) {
    super();

    this.dao = dao;
  }

  public List<Ergebniseingang> findByNeuesteEingaengeByWahlAndErgebniseingangsart(@Nonnull Wahl wahl, @Nonnull Ergebniseingangsart ergebniseingangsart) {
    return findByNeuesteEingaengeByWahlAndErgebniseingangsartAndErgebniseingabestufe(wahl, ergebniseingangsart, EnumSet.allOf(Ergebniseingabestufe.class));
  }

  public List<Ergebniseingang> findByNeuesteEingaengeByWahlAndErgebniseingangsartAndErgebniseingabestufe(@Nonnull Wahl wahl, @Nonnull Ergebniseingangsart ergebniseingangsart,
      Ergebniseingabestufe ergebniseingabestufe) {
    return findByNeuesteEingaengeByWahlAndErgebniseingangsartAndErgebniseingabestufe(wahl, ergebniseingangsart, EnumSet.of(ergebniseingabestufe));
  }

  // ELECT-6934
  public List<Ergebniseingang> findByNeuesteEingaengeByWahlAndErgebniseingangsartAndErgebniseingabestufe(@Nonnull Wahl wahl, @Nonnull Ergebniseingangsart ergebniseingangsart,
      Collection<Ergebniseingabestufe> ergebniseingabestufen) {
    boolean ergebniseingabestufenIgnored = ergebniseingabestufen.isEmpty() || ergebniseingabestufen.containsAll(EnumSet.allOf(Ergebniseingabestufe.class));

    if (DbInfo.getDbInformation().getDbType().isSupportsMultipleColumnsInSubQuery()) {
      if (ergebniseingabestufenIgnored) {
        return dao.findByNeuesteEingaengeByWahlAndErgebniseingangsartDbms(wahl, ergebniseingangsart);
      }

      return dao.findByNeuesteEingaengeByWahlAndErgebniseingangsartAndErgebniseingabestufeDbms(wahl, ergebniseingangsart, ergebniseingabestufen);
    }

    if (ergebniseingabestufenIgnored) {
      return dao.findByNeuesteEingaengeByWahlAndErgebniseingangsartH2(wahl, ergebniseingangsart);
    }

    return dao.findByNeuesteEingaengeByWahlAndErgebniseingangsartAndErgebniseingabestufeH2(wahl, ergebniseingangsart, ergebniseingabestufen);
  }

  @Nullable
  public Ergebniseingang findByNeuesterEingangByGebietUndErgebniseingangsart(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart) {
    return findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingabestufe(wahldurchfuehrungsgebiet, ergebniseingangsart, EnumSet.allOf(Ergebniseingabestufe.class));
  }

  @Nullable
  public Ergebniseingang findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingabestufe(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart,
      Ergebniseingabestufe ergebniseingabestufe) {
    return findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingabestufe(wahldurchfuehrungsgebiet, ergebniseingangsart, EnumSet.of(ergebniseingabestufe));
  }

  @Nullable
  public Ergebniseingang findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingabestufe(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart,
      Collection<Ergebniseingabestufe> ergebniseingabestufen) {
    boolean ergebniseingabestufenIgnored = ergebniseingabestufen.isEmpty() || ergebniseingabestufen.containsAll(EnumSet.allOf(Ergebniseingabestufe.class));

    Collection<Ergebniseingangsart> ergebniseingangsarten = EnumSet.of(ergebniseingangsart);

    if (ergebniseingabestufenIgnored) {
      return letzterEingang(dao.findByNeuesterEingangByGebietAndErgebniseingangsart(wahldurchfuehrungsgebiet, ergebniseingangsarten));
    }

    return letzterEingang(dao.findByNeuesterEingangByGebietAndErgebniseingangsartAndErgebniseingabestufe(wahldurchfuehrungsgebiet, ergebniseingangsarten, ergebniseingabestufen));
  }

  public Ergebniseingang findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingangsfehlerstatus(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet, Ergebniseingangsart ergebniseingangsart,
      Collection<Ergebniseingangsfehlerstatus> ergebniseingangsfehlerstatus) {
    return findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingabestufeAndErgebniseingangsfehlerstatus(wahldurchfuehrungsgebiet, ergebniseingangsart,
        EnumSet.allOf(Ergebniseingabestufe.class), ergebniseingangsfehlerstatus);
  }

  public Ergebniseingang findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingabestufeAndErgebniseingangsfehlerstatus(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet,
      Ergebniseingangsart ergebniseingangsart, Ergebniseingabestufe ergebniseingabestufe, Collection<Ergebniseingangsfehlerstatus> ergebniseingangsfehlerstatus) {
    return findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingabestufeAndErgebniseingangsfehlerstatus(wahldurchfuehrungsgebiet, ergebniseingangsart, EnumSet.of(ergebniseingabestufe),
        ergebniseingangsfehlerstatus);
  }

  private Ergebniseingang findByNeuesterEingangByGebietUndErgebniseingangsartAndErgebniseingabestufeAndErgebniseingangsfehlerstatus(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet,
      Ergebniseingangsart ergebniseingangsart, Collection<Ergebniseingabestufe> ergebniseingabestufen, Collection<Ergebniseingangsfehlerstatus> ergebniseingangsfehlerstatus) {
    boolean ergebniseingabestufenIgnored = ergebniseingabestufen.isEmpty() || ergebniseingabestufen.containsAll(EnumSet.allOf(Ergebniseingabestufe.class));

    Collection<Ergebniseingangsart> ergebniseingangsarten = EnumSet.of(ergebniseingangsart);

    if (ergebniseingabestufenIgnored) {
      return letzterEingang(dao.findByNeuesterEingangByGebietAndErgebniseingangsartAndFehlerstatus(wahldurchfuehrungsgebiet, ergebniseingangsarten, ergebniseingangsfehlerstatus));
    }

    return letzterEingang(dao.findByNeuesterEingangByGebietAndErgebniseingangsartAndFehlerstatusAndErgebniseingabestufe(wahldurchfuehrungsgebiet, ergebniseingangsarten, ergebniseingangsfehlerstatus,
        ergebniseingabestufen));
  }

  /**
   * Sollte praktischer Weise niemals vorkommen, dass hier zwei Eingänge auftauchen ELECT-4138 Damit die Erfassung weitergehen kann, wenn der Fall doch eintritt, den Ergebniseingang mit der höchsten
   * ID nehmen
   */
  @Nullable
  private Ergebniseingang letzterEingang(List<Ergebniseingang> moeglicheLetzteEingaenge) {
    if (moeglicheLetzteEingaenge.isEmpty()) {
      return null;
    }

    if (moeglicheLetzteEingaenge.size() == 1) {
      return moeglicheLetzteEingaenge.get(0);
    }

    LOG.error("Zwei Ergebniseingaenge mit der selben Eingangssequenznummer!");

    Ergebniseingang ergebniseingangHoechsteId = null; // Im Zweifel den Eingang mit der höchsten Id zurückliefern

    for (Ergebniseingang ergebniseingang : moeglicheLetzteEingaenge) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = ergebniseingang.getWahldurchfuehrungsgebiet();
      Wahl wahl = wahldurchfuehrungsgebiet.getWahl();

      LOG.error("Wahl-ID: " + wahl.getId());
      LOG.error("Wahl: " + wahl.getName());
      LOG.error("Gebiet-ID: " + wahldurchfuehrungsgebiet.getId());
      LOG.error("Gebiet-Name: " + wahldurchfuehrungsgebiet.getName());
      LOG.error("Gebiet-Nummer: " + wahldurchfuehrungsgebiet.getNummer());
      LOG.error("Ergebniseingang-ID: " + ergebniseingang.getId());
      LOG.error("Ergebniseingangsart: " + ergebniseingang.getErgebniseingangsart());
      LOG.error("Eingangssequenznummer: " + ergebniseingang.getEingangssequenznummer());

      if (ergebniseingangHoechsteId == null || ergebniseingangHoechsteId.getId() < ergebniseingang.getId()) {
        ergebniseingangHoechsteId = ergebniseingang;
      }
    }

    LOG.error("Waehle Ergebniseingang: " + ergebniseingangHoechsteId.getId());

    return ergebniseingangHoechsteId;
  }

  public static ErgebniseingangDAOHelper from(AbstractErgebniseingangDAO dao) {
    return new ErgebniseingangDAOHelper(dao);
  }
}
