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

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.ejb.Stateless;
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.boundary.StimmenDAO;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.internet.control.PresentationGenerieren;
import de.ivu.elect.business.protokollierung.entity.LoeschProtokollEvent;
import de.ivu.elect.business.stimmeingabe.control.ErgebniseingangEingangssequenznummerGenerator;
import de.ivu.elect.business.stimmeingabe.control.GeloeschteErgebniseingangEingangssequenznummerSpeichern;
import de.ivu.elect.business.stimmeingabe.control.LetzterEingangCache;
import de.ivu.elect.business.stimmeingabe.entity.ErgebnisGueltigerEingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestatus;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestufe;
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.Stimmeingabefehler;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;


import static java.util.Collections.singletonList;

@Stateless
public class StimmeingabeErfassungLoeschen implements Serializable {

  private static final long serialVersionUID = 1L;

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

  @Inject
  private transient ErgebnisDAO ergebnisDAO;

  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  @Inject
  private transient StimmenDAO stimmenDAO;

  @Inject
  private transient ErgebniseingangDAO ergebniseingangDAO;

  @Inject
  private transient StimmeingabeDAO stimmeingabeDAO;

  @Inject
  private transient StimmeingabefehlerDAO stimmeingabefehlerDAO;

  @Inject
  private transient ErgebnisGueltigerEingangDAO ergebnisGueltigerEingangDAO;

  @Inject
  private transient ErgebniseingangExporter ergebniseingangExporter;

  @Inject
  private transient Ergebnisberechnung ergebnisberechnung;

  @Inject
  private transient ErgebniseingangEingangssequenznummerGenerator ergebniseingangEingangssequenznummerGenerator;

  @Inject
  private transient GeloeschteErgebniseingangEingangssequenznummerSpeichern geloeschteErgebniseingangEingangssequenznummerSpeichern;

  @Inject
  private transient LoeschProtokollEvent loeschProtokollEvent;

  @Inject
  private transient Messages messages;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient PresentationGenerieren presentationGenerieren;

  @Inject
  private transient LetzterEingangCache letzterEingangCache;

  public StimmeingabeErfassungLoeschen() {
    super();
  }

  // ELECTMUC-393
  public void eingabenLoeschen(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebietNotAttached, Ergebnis ergebnisNotAttached, ErgebniseingangErfasser geloeschtDurch) {
    eingabenLoeschen(singletonList(wahldurchfuehrungsgebietNotAttached), ergebnisNotAttached, geloeschtDurch);
  }

  public void eingabenLoeschen(Collection<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebieteNotAttached, Ergebnis ergebnisNotAttached, ErgebniseingangErfasser geloeschtDurch) {
    eingabenLoeschen(wahldurchfuehrungsgebieteNotAttached, ergebnisNotAttached, geloeschtDurch, Arrays.asList(Ergebniseingabestufe.values()));
  }

  public void eingabenLoeschen(Collection<Wahldurchfuehrungsgebiet> wahldurchfuehrungsgebieteNotAttached, Ergebnis ergebnisNotAttached, ErgebniseingangErfasser geloeschtDurch,
      Collection<Ergebniseingabestufe> ergebniseingabestufen) {
    if (!wahldurchfuehrungsgebieteNotAttached.isEmpty()) {
      Ergebnis ergebnis = attach(ergebnisNotAttached);
      Ergebnisart ergebnisart = ergebnis.getErgebnisart();
      Ergebniseingangsart ergebniseingangsart = Ergebniseingangsart.getErgebniseingangsartFuerErgebnisArt(ergebnisart);

      Map<Wahldurchfuehrungsgebiet, Boolean> ergebniseingaengeGeloeschtPerWahldurchfuehrungsgebiet = new HashMap<>();

      Date geloeschtAm = new Date();

      wahldurchfuehrungsgebieteNotAttached.stream() //
          .map(this::attach) //
          .forEach(wahldurchfuehrungsgebiet -> {
            LOG.info("Lösche Ergebniseingänge und Stimmen [ergebnis = " + ergebnis + ", gebiet = " + wahldurchfuehrungsgebiet + ", ergebnisArt = " + ergebnisart + ", erfasser = "
                + geloeschtDurch.getKennung() + ", ergebniseingangsart = " + ergebniseingangsart + ", zeitpunkt = " + geloeschtAm + "]");

            ergebniseingaengeGeloeschtPerWahldurchfuehrungsgebiet.put(wahldurchfuehrungsgebiet, false);

            LOG.info("Lösche Ergebniseingänge - start");
            List<Ergebniseingang> ergebniseingaengeByErgebnisAndGebietAndEingabestufe = ergebniseingangDAO.findByGebietAndErgebniseingangsartAndErgebniseingangstufe(wahldurchfuehrungsgebiet,
                ergebniseingangsart, ergebniseingabestufen);

            // wahldurchfuehrungsgebiet.resetErgebniseingang(ergebnis); // ELECT-8883
            letzterEingangCache.reset(wahldurchfuehrungsgebiet.getWahl().getPk(), wahldurchfuehrungsgebiet.getPk());

            for (Ergebniseingang ergebniseingang : ergebniseingaengeByErgebnisAndGebietAndEingabestufe) {
              LOG.info("\tLösche Ergebniseingang - start [ergebniseingang = {}]", ergebniseingang);

              LOG.info("\t\tLösche gültige Ergebniseingänge - start");
              List<ErgebnisGueltigerEingang> ergebnisGueltigerEingaenge = ergebnisGueltigerEingangDAO.findByErgebniseingang(ergebniseingang);

              // ELECT-4157
              for (ErgebnisGueltigerEingang ergebnisGueltigerEingang : ergebnisGueltigerEingaenge) {
                ergebnisGueltigerEingangDAO.remove(ergebnisGueltigerEingang);
              }
              LOG.info("\t\tLösche gültige Ergebniseingänge - done [anzahl = {}]", ergebnisGueltigerEingaenge.size());

              ergebniseingaengeGeloeschtPerWahldurchfuehrungsgebiet.put(wahldurchfuehrungsgebiet, true);

              preErgebniseingangLoeschen(geloeschtDurch, geloeschtAm, ergebniseingang);

              LOG.info("\t\tLösche Stimmeingaben - start");
              List<Stimmeingabe> stimmeingaben = new ArrayList<>(ergebniseingang.getStimmeingaben());

              // ELECT-10813, Stimmeingaben und Stimmeingabefehler explizit löschen!
              for (Stimmeingabe stimmeingabe : stimmeingaben) {
                List<Stimmeingabefehler> stimmeingabefehlers = new ArrayList<>(stimmeingabe.getStimmeingabefehler());

                for (Stimmeingabefehler stimmeingabefehler : stimmeingabefehlers) {
                  stimmeingabefehlerDAO.remove(stimmeingabefehler);
                }

                stimmeingabeDAO.remove(stimmeingabe);
              }
              LOG.info("\t\tLösche Stimmeingaben - done [anzahl = {}]", stimmeingaben.size());

              ergebniseingangDAO.remove(ergebniseingang);
              LOG.info("\tLösche Ergebniseingang - done [ergebniseingang = {}]", ergebniseingang);
            }
            LOG.info("Lösche Ergebniseingänge - done [anzahl = {}]", ergebniseingaengeByErgebnisAndGebietAndEingabestufe.size());

            LOG.info("Lösche Stimmen - start");
            List<Stimmen> stimmenByErgebnisAndGebiet = stimmenDAO.findByErgebnisAndGebietNoCache(ergebnis, wahldurchfuehrungsgebiet);

            for (Stimmen stimmen : stimmenByErgebnisAndGebiet) {
              stimmenDAO.remove(stimmen);
            }
            LOG.info("Lösche Stimmen - done [anzahl = {}]", stimmenByErgebnisAndGebiet.size());

            if (wahldurchfuehrungsgebiet.getErgebniseingabestatus(ergebnisart).isEingabeMoeglichStatus()) {
              wahldurchfuehrungsgebiet.setErgebniseingabestatus(Ergebniseingabestatus.EINGABE_MOEGLICH, ergebnisart); // ELECT-9200
            }
          });

      postErgebniseingaengeLoeschen(geloeschtDurch, geloeschtAm, ergebniseingaengeGeloeschtPerWahldurchfuehrungsgebiet);

      presentationGenerieren.preasentationFuerErgebnisZurueckSetzen(ergebnis);

      final Long[] letzteGeloeschteEingangssequenznummer = {null};

      wahldurchfuehrungsgebieteNotAttached.stream() //
          .map(this::attach) //
          .filter(wahldurchfuehrungsgebiet -> ergebniseingaengeGeloeschtPerWahldurchfuehrungsgebiet.containsKey(wahldurchfuehrungsgebiet)
              && ergebniseingaengeGeloeschtPerWahldurchfuehrungsgebiet.get(wahldurchfuehrungsgebiet))
          .forEach(wahldurchfuehrungsgebiet -> {
            Long naechsteEingangssequenznummer = ergebniseingangEingangssequenznummerGenerator.getNaechsteEingangssequenznummer();

            letzteGeloeschteEingangssequenznummer[0] = naechsteEingangssequenznummer;

            // Gelöschter Ergebniseingang enthält keine Stimmeingaben, vergleiche de.ivu.elect.was.exchange.stimmeingabe.WasErgebniseingangXMLZIPExportMapper.
            Ergebniseingang ergebniseingangGeloescht = new Ergebniseingang();
            ergebniseingangGeloescht.setWahldurchfuehrungsgebiet(wahldurchfuehrungsgebiet);
            ergebniseingangGeloescht.setErfasser(geloeschtDurch.getKennung());
            ergebniseingangGeloescht.setZeitstempel(new Date());
            ergebniseingangGeloescht.setEingangssequenznummer(naechsteEingangssequenznummer);
            ergebniseingangGeloescht.setErgebniseingangsart(ergebniseingangsart);
            ergebniseingangGeloescht.setErgebniseingangsherkunft(Ergebniseingangsherkunft.BENUTZER_EINGABE);
            ergebniseingangGeloescht.setGueltigKeinFehler();

            LOG.info("Lösche Ergebniseingänge - Ergebniseingang gelöscht - Eingangssequenznummer: " + naechsteEingangssequenznummer);

            ergebniseingangExporter.export(ergebniseingangGeloescht);
          });

      if (letzteGeloeschteEingangssequenznummer[0] != null) {
        geloeschteErgebniseingangEingangssequenznummerSpeichern.speichereEingangssequenznummerInKonfiguration();
      }

      triggerZwischenergebnisberechnung(ergebnis);
    }
  }

  protected void preErgebniseingangLoeschen(ErgebniseingangErfasser geloeschtDurch, Date geloeschtAm, Ergebniseingang ergebniseingang) {
    loeschProtokollEvent.sendeLoeschProtokollEvent(ergebniseingang.getWahldurchfuehrung(), ergebniseingang, "loesch.protokoll.ergebniseingang",
        formatGebietDarstellung(ergebniseingang.getWahldurchfuehrungsgebiet()), getMessage(ergebniseingang.getErgebniseingangsart()));
  }

  protected void postErgebniseingaengeLoeschen(ErgebniseingangErfasser geloeschtDurch, Date geloeschtAm, Map<Wahldurchfuehrungsgebiet, Boolean> ergebniseingaengeGeloeschtPerWahldurchfuehrungsgebiet) {
    // nichts zu tun
  }

  private void triggerZwischenergebnisberechnung(Ergebnis ergebnis) {
    ergebnisberechnung.ergebnisBerechnenAusloesenUebernahmeGueltigerEingaenge(ergebnis); // Übernahme wichtig, Destatis ERG-Datei Export funktioniert sonst nicht beim Löschen des letten Eingangs...
  }

  private Ergebnis attach(Ergebnis ergebnis) {
    return ergebnisDAO.attach(ergebnis);
  }

  private Wahldurchfuehrungsgebiet attach(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return wahldurchfuehrungsgebietDAO.attach(wahldurchfuehrungsgebiet);
  }

  protected String getMessage(String msgKey, Object... params) {
    return messages.getMessageForMessageKey(msgKey, params);
  }

  protected String getMessage(Enum< ? > enumValue) {
    return messages.lokalisiere(enumValue);
  }

  protected String formatGebietDarstellung(Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet) {
    return gebietNummerFormatter.getDarstellung(wahldurchfuehrungsgebiet);
  }
}
