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

import static de.ivu.elect.jee.entity.EntityUtil.castTo;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier;
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.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppe;
import de.ivu.elect.business.gruppe.entity.SystemGruppeEigenschaften;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietDAO;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.kandidatur.entity.AbstractKandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.stimmeingabe.boundary.AbstractListenwahlStimmeingabeGruppeStimmenModelBuilder;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeAufGebietMoeglichChecker;
import de.ivu.elect.business.stimmeingabe.boundary.StimmeingabeImporter;
import de.ivu.elect.business.stimmeingabe.control.KommunalListenwahlStimmeingabeKonfiguration;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeImportContext;
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.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.ZwischensummeEingabe;
import de.ivu.elect.business.stimmzettel.control.StimmzettelStimmenBerechner;
import de.ivu.elect.business.stimmzettel.entity.AbstractStimmzettelKopfdatum;
import de.ivu.elect.business.stimmzettel.entity.Stimmzettel;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelDaten;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelDatenFactory;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatum;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatumArt;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlParameterLoader;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagDAO;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;

@Stateless
public class ErgebniseingangAusStimmzettelCreate implements Serializable {

  private static final long serialVersionUID = 1L;

  public static final Ergebniseingangsherkunft ERGEBNISEINGANGSHERKUNFT_FUER_ERGEBNISEINGANG = Ergebniseingangsherkunft.STIMMZETTEL_ERFASSUNG;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  private transient StimmeingabeAufGebietMoeglichChecker stimmeingabeAufGebietMoeglichChecker;

  @Inject
  private transient StimmzettelStimmenBerechner stimmzettelStimmenBerechner;

  @Inject
  private transient StimmzettelDatenFactory stimmzettelDatenFactory;

  @Inject
  private transient StimmzettelDAO stimmzettelDAO;

  @Inject
  private transient StimmzettelKopfdatumDAO stimmzettelKopfdatumDAO;

  @Inject
  private transient EntityAufStimmzettelDatenDAOFactory entityAufStimmzettelDatenDAOFactory;

  @Inject
  private transient WahlvorschlagDAO wahlvorschlagDAO;

  @Inject
  private transient GruppeImGebietDAO gruppeImGebietDAO;

  @Inject
  private transient SystemGruppeEigenschaften systemGruppeEigenschaften;

  @Inject
  private transient StimmeingabeImporter stimmeingabeImporter;

  @Inject
  private transient KommunalListenwahlStimmeingabeKonfiguration kommunalListenwahlStimmeingabeKonfiguration;

  @Inject
  private transient WahlParameterLoader wahlParameterLoader;

  // ELECTKOM-884
  public boolean ergebniseingangAnlegenWennBereitsVorhanden(Gebiet gebietNotAttached, ErgebniseingangErfasser erfasser, BooleanSupplier eingangsherkunftIgnorieren) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = wahldurchfuehrungsgebietDAO.findByPrimaryKey(gebietNotAttached.getPk());
    if (wahldurchfuehrungsgebiet.getErgebniseingang(getErgebniseingangsart(wahldurchfuehrungsgebiet).korrespondierendeErgebnisart()) != null) {
      // Eingang vorhanden => neuen anlegen
      return ergebniseingangAnlegen(wahldurchfuehrungsgebiet, erfasser, eingangsherkunftIgnorieren);
    }
    return false;
  }

  public boolean ergebniseingangAnlegen(Gebiet gebietNotAttached, ErgebniseingangErfasser erfasser, BooleanSupplier eingangsherkunftIgnorieren) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = wahldurchfuehrungsgebietDAO.findByPrimaryKey(gebietNotAttached.getPk());
    if (wahldurchfuehrungsgebiet.isErfassungsgebiet() && isStimmeingabeMoeglich(wahldurchfuehrungsgebiet, eingangsherkunftIgnorieren)) {
      Wahl wahl = wahldurchfuehrungsgebiet.getWahl();

      StimmzettelDaten stimmzettelDaten = stimmzettelDatenFactory.create(entityAufStimmzettelDatenDAOFactory.getEntityAufStimmzettelDaten(), wahldurchfuehrungsgebiet,
          wahlvorschlagDAO.findByWahl(wahl));
      List<Stimmzettel> stimmzettelListe = stimmzettelDAO.findByGebiet(wahldurchfuehrungsgebiet);
      List<StimmzettelKopfdatum> stimmzettelKopfdaten = stimmzettelKopfdatumDAO.findByGebiet(wahldurchfuehrungsgebiet);

      boolean stimmzettelNurEinzelnerWahlvorschlagUnveraendertManuellAuszuzaehlen = kommunalListenwahlStimmeingabeKonfiguration
          .isStimmzettelNurEinzelnerWahlvorschlagUnveraendertManuellAuszuzaehlen(wahlParameterLoader, wahl, getErgebnisart(wahldurchfuehrungsgebiet));

      Map<Listenkandidatur, Long> stimmenFuerListenkandidaturen = stimmzettelStimmenBerechner.werteStimmzettelAus(stimmzettelDaten, wahldurchfuehrungsgebiet, stimmzettelListe, stimmzettelKopfdaten,
          stimmzettelNurEinzelnerWahlvorschlagUnveraendertManuellAuszuzaehlen);

      Map<Gruppe, List<Listenkandidatur>> listenkandidaturenNachGruppe = stimmenFuerListenkandidaturen.keySet().stream().collect(Collectors.groupingBy(AbstractKandidatur::getGruppe));

      Map<Liste, Long> anzahlUnveraenderteStimmzettelProListe = stimmzettelStimmenBerechner.bestimmeAnzahlStimmzettelUnveraendert(stimmzettelDaten, wahldurchfuehrungsgebiet, stimmzettelListe,
          stimmzettelKopfdaten);

      Map<Liste, Long> anzahlVeraenderteStimmzettelProListe = stimmzettelStimmenBerechner.bestimmeAnzahlStimmzettelVeraendert(stimmzettelDaten, wahldurchfuehrungsgebiet, stimmzettelListe);

      Map<Gruppe, Liste> listenNachGruppe = new HashMap<>();

      for (Liste liste : anzahlUnveraenderteStimmzettelProListe.keySet()) {
        listenNachGruppe.put(liste.getGruppe(), liste);
      }

      for (Liste liste : anzahlVeraenderteStimmzettelProListe.keySet()) {
        listenNachGruppe.put(liste.getGruppe(), liste);
      }

      Map<PolitischeGruppe, Long> zweitstimmenAktuellGesamt = stimmzettelStimmenBerechner.getZweitstimmenAktuellGesamt(stimmzettelDaten, stimmenFuerListenkandidaturen::get);

      Ergebniseingang ergebniseingang = new Ergebniseingang();
      ergebniseingang.setWahldurchfuehrungsgebiet(wahldurchfuehrungsgebiet);
      ergebniseingang.setErfasser(erfasser.getKennung());
      ergebniseingang.setErgebniseingangsart(getErgebniseingangsart(wahldurchfuehrungsgebiet));
      ergebniseingang.setErgebniseingangsherkunft(ERGEBNISEINGANGSHERKUNFT_FUER_ERGEBNISEINGANG);

      List<GruppeImGebiet> gruppeImGebiets = gruppeImGebietDAO.findByGebiet(wahldurchfuehrungsgebiet);

      for (GruppeImGebiet gruppeImGebiet : gruppeImGebiets) {
        if (isWaehler(gruppeImGebiet)) {
          createStimmeingabe(ergebniseingang, gruppeImGebiet, Stimmenart.GESAMT,
              stimmzettelStimmenBerechner.getWaehlerGesamt(kopfdatumArt -> getWertAusStimmzettelKopfdaten(kopfdatumArt, stimmzettelKopfdaten)));

          createStimmeingabe(ergebniseingang, gruppeImGebiet, Stimmenart.MIT_WAHLSCHEIN,
              stimmzettelStimmenBerechner.getWaehlerMitWahlschein(kopfdatumArt -> getWertAusStimmzettelKopfdaten(kopfdatumArt, stimmzettelKopfdaten)));

          createStimmeingabe(ergebniseingang, gruppeImGebiet, Stimmenart.OHNE_WAHLSCHEIN,
              stimmzettelStimmenBerechner.getWaehlerOhneWahlschein(kopfdatumArt -> getWertAusStimmzettelKopfdaten(kopfdatumArt, stimmzettelKopfdaten)));
        } else if (isWahlberechtigte(gruppeImGebiet)) {
          createStimmeingabe(ergebniseingang, gruppeImGebiet, Stimmenart.GESAMT,
              stimmzettelStimmenBerechner.getWahlberechtigteGesamt(kopfdatumArt -> getWertAusStimmzettelKopfdaten(kopfdatumArt, stimmzettelKopfdaten)));

          createStimmeingabe(ergebniseingang, gruppeImGebiet, Stimmenart.MIT_WAHLSCHEIN,
              stimmzettelStimmenBerechner.getWahlberechtigteMitWahlschein(kopfdatumArt -> getWertAusStimmzettelKopfdaten(kopfdatumArt, stimmzettelKopfdaten)));

          createStimmeingabe(ergebniseingang, gruppeImGebiet, Stimmenart.OHNE_WAHLSCHEIN,
              stimmzettelStimmenBerechner.getWahlberechtigteOhneWahlschein(kopfdatumArt -> getWertAusStimmzettelKopfdaten(kopfdatumArt, stimmzettelKopfdaten)));

          createStimmeingabe(ergebniseingang, gruppeImGebiet, Stimmenart.NICHT_IM_WAEHLERVERZEICHNIS,
              stimmzettelStimmenBerechner.getWahlberechtigteNichtImWaehlerverzeichnis(kopfdatumArt -> getWertAusStimmzettelKopfdaten(kopfdatumArt, stimmzettelKopfdaten)));
        } else if (isUngueltige(gruppeImGebiet)) {
          Long stimmzettelUngueltig = stimmzettelStimmenBerechner.getStimmzettelUngueltig(stimmzettelListe, stimmzettelKopfdaten);
          createStimmeingabe(ergebniseingang, gruppeImGebiet, Stimmenart.LISTENSTIMME/* ungueltige Stimmzettel */, stimmzettelUngueltig);
        } else if (isGueltige(gruppeImGebiet)) {
          // egal
        } else if (isPolitischeGruppe(gruppeImGebiet)) {
          Liste liste = listenNachGruppe.get(gruppeImGebiet.getGruppe());
          if (liste != null) {

            Long unveraendert = anzahlUnveraenderteStimmzettelProListe.get(liste);
            Long veraendert = anzahlVeraenderteStimmzettelProListe.get(liste);
            // Nur MUC
            Long gesamt = addiere(unveraendert, veraendert);

            // SUPER DUPER München Spezialquatsch
            if (ergebniseingang.getErgebniseingangsart().isVorlaeufig()) {
              // see de.ivu.elect.business.stimmeingabe.boundary.ByKommunalListenwahlStimmeingabeErfassenModelBuilder.createStimmzetteleingabeGruppeStimmenNurEinzelnerWahlvorschlagGesamt
              if (kommunalListenwahlStimmeingabeKonfiguration.isStimmzetteleingabeGruppeStimmenNurEinzelnerWahlvorschlagGesamtRelevant(wahlParameterLoader, wahl,
                  getErgebnisart(wahldurchfuehrungsgebiet))) {
                if (!kommunalListenwahlStimmeingabeKonfiguration.isErgebnismeldungVorlaeufigKomplett(wahlParameterLoader, wahl, getErgebnisart(wahldurchfuehrungsgebiet))) {
                  unveraendert = gesamt; // HACK, aber funktioniert
                }
              }
            }

            createStimmeingabe(ergebniseingang, gruppeImGebiet, Stimmenart.LISTENSTIMME, unveraendert);

            createZwischensummeEingabe(ergebniseingang, gruppeImGebiet, Stimmenart.LISTENSTIMME,
                AbstractListenwahlStimmeingabeGruppeStimmenModelBuilder.GUELTIGE_LISTENSTIMMEN_NUR_EINZELNER_WAHLVORSCHLAG_VERAENDERT_ZWISCHENSUMME_INDEX, veraendert);

            createZwischensummeEingabe(ergebniseingang, gruppeImGebiet, Stimmenart.LISTENSTIMME,
                AbstractListenwahlStimmeingabeGruppeStimmenModelBuilder.GUELTIGE_LISTENSTIMMEN_NUR_EINZELNER_WAHLVORSCHLAG_GESAMT_ZWISCHENSUMME_INDEX, gesamt);

            Long kummuliertPanaschiertGesamt = zweitstimmenAktuellGesamt.get(gruppeImGebiet.getGruppe().castTo(PolitischeGruppe.class).getEntity());
            createZwischensummeEingabe(ergebniseingang, gruppeImGebiet, Stimmenart.LISTENSTIMME,
                AbstractListenwahlStimmeingabeGruppeStimmenModelBuilder.GUELTIGE_LISTENSTIMMEN_LISTENKANDIDATUREN_GESAMT_ZWISCHENSUMME_INDEX, kummuliertPanaschiertGesamt);

            for (Listenkandidatur listenkandidatur : listenkandidaturenNachGruppe.getOrDefault(gruppeImGebiet.getGruppe(), Collections.emptyList())) {
              Stimmeingabe stimmeingabe = createStimmeingabe(ergebniseingang, gruppeImGebiet, Stimmenart.LISTENSTIMME, stimmenFuerListenkandidaturen.get(listenkandidatur));
              stimmeingabe.setListenkandidatur(listenkandidatur);
            }
          }
        }
      }

      StimmeingabeImportContext context = new StimmeingabeImportContext(castTo(wahl, Wahldurchfuehrung.class), erfasser);
      DateiImportBerichtCustomData customData = new DateiImportBerichtCustomData();
      StringBuilder bericht = new StringBuilder();
      stimmeingabeImporter.persistiere(Collections.singletonList(ergebniseingang), context, customData, bericht);
      return true;
    }
    return false;
  }

  private Long addiere(Long a, Long b) {
    if (a == null) {
      return b;
    }
    if (b == null) {
      return a;
    }
    return a + b;

  }

  private boolean isPolitischeGruppe(GruppeImGebiet gruppeImGebiet) {
    return gruppeImGebiet.getGruppe().isPolitischeGruppe();
  }

  private boolean isWaehler(GruppeImGebiet gruppeImGebiet) {
    return gruppeImGebiet.getGruppe().isSystemGruppe() && systemGruppeEigenschaften.isGruppeEqualsSystemGruppe(gruppeImGebiet.getGruppe(), SystemGruppe.WAEHLER);
  }

  private boolean isWahlberechtigte(GruppeImGebiet gruppeImGebiet) {
    return gruppeImGebiet.getGruppe().isSystemGruppe() && systemGruppeEigenschaften.isGruppeEqualsSystemGruppe(gruppeImGebiet.getGruppe(), SystemGruppe.WAHLBERECHTIGTE);
  }

  private boolean isGueltige(GruppeImGebiet gruppeImGebiet) {
    return gruppeImGebiet.getGruppe().isSystemGruppe() && systemGruppeEigenschaften.isGruppeEqualsSystemGruppe(gruppeImGebiet.getGruppe(), SystemGruppe.GUELTIGE);
  }

  private boolean isUngueltige(GruppeImGebiet gruppeImGebiet) {
    return gruppeImGebiet.getGruppe().isSystemGruppe() && systemGruppeEigenschaften.isGruppeEqualsSystemGruppe(gruppeImGebiet.getGruppe(), SystemGruppe.UNGUELTIGE);
  }

  private Stimmeingabe createStimmeingabe(Ergebniseingang ergebniseingang, GruppeImGebiet gruppeImGebiet, Stimmenart stimmenart, Long anzahl) {
    Stimmeingabe stimmeingabe = new Stimmeingabe();
    stimmeingabe.setErgebniseingang(ergebniseingang);
    stimmeingabe.setStimmenart(stimmenart);
    stimmeingabe.setGruppeImGebiet(gruppeImGebiet);
    if (anzahl != null) {
      stimmeingabe.setAnzahl(anzahl);
    }
    return stimmeingabe;
  }

  private void createZwischensummeEingabe(Ergebniseingang ergebniseingang, GruppeImGebiet gruppeImGebiet, Stimmenart stimmenart, Long summenIndex, Long anzahl) {
    ZwischensummeEingabe zwischensummeEingabe = new ZwischensummeEingabe();
    zwischensummeEingabe.setErgebniseingang(ergebniseingang);
    zwischensummeEingabe.setStimmenart(stimmenart);
    zwischensummeEingabe.setSummenIndex(summenIndex);
    zwischensummeEingabe.setGruppeImGebiet(gruppeImGebiet);
    if (anzahl != null) {
      zwischensummeEingabe.setAnzahl(anzahl);
    }
  }

  @Nullable
  private Long getWertAusStimmzettelKopfdaten(StimmzettelKopfdatumArt stimmzettelKopfdatumArt, List<StimmzettelKopfdatum> stimmzettelKopfdaten) {
    return stimmzettelKopfdaten.stream().filter(stimmzettelKopfdatum -> stimmzettelKopfdatumArt.equals(stimmzettelKopfdatum.getStimmzettelKopfdatumArt())).findFirst()
        .map(AbstractStimmzettelKopfdatum::parseWertAsLong).orElse(null);
  }

  public boolean isStimmeingabeMoeglich(Gebiet gebietNotAttached, BooleanSupplier eingangsherkunftIgnorieren) {
    Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = wahldurchfuehrungsgebietDAO.findByPrimaryKey(gebietNotAttached.getPk());
    return stimmeingabeAufGebietMoeglichChecker.isStimmeingabeUndErfassenAufGebietMoeglich(wahldurchfuehrungsgebiet, getErgebnisart(wahldurchfuehrungsgebiet),
        ERGEBNISEINGANGSHERKUNFT_FUER_ERGEBNISEINGANG, eingangsherkunftIgnorieren);
  }

  private Ergebnisart getErgebnisart(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return getErgebniseingangsart(wahldurchfuehrungsgebiet).korrespondierendeErgebnisart();
  }

  private Ergebniseingangsart getErgebniseingangsart(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return wahldurchfuehrungsgebiet.getWahl().castTo(Wahldurchfuehrung.class).getEntity().getErgebniseingangsart();
  }
}
