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

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

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

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

import de.ivu.elect.business.gebietsbaum.boundary.GebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.GruppeUK;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietDAO;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebietUK;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.mandant.control.MandantSupport;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabe;
import de.ivu.elect.business.stimmeingabe.entity.Stimmeingabefehler;
import de.ivu.elect.business.stimmeingabe.entity.ZwischensummeEingabe;
import de.ivu.elect.business.wahl.boundary.WahlDAO;
import de.ivu.elect.business.wahlvorschlag.boundary.ListeDAO;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.jee.exception.ElectCommonException;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;
import de.ivu.elect.presentation.mandant.MandantNameFormatter;

/**
 * ELECT-8023
 */
public class WahlaufteilenStimmeingabeCreate extends AbstractElectStimmeingabeCreate {

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

  @Inject
  private transient WahlDAO wahlDAO;

  @Inject
  private transient GruppeImGebietDAO gruppeImGebietDAO;

  @Inject
  private transient GebietDAO gebietDAO;

  @Inject
  private transient MandantNameFormatter mandantNameFormatter;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient ListeDAO listeDAO;

  @Inject
  private transient MandantSupport mandantSupport;

  public void saveFuerZielWahl(Ergebniseingang ergebniseingang) {
    if (mandantSupport != null/* Tests */ && mandantSupport.isMandantenUnterstuetzt() /* nur bei Mandantensystemen relevant */) {
      Wahldurchfuehrungsgebiet quellGebiet = ergebniseingang.getWahldurchfuehrungsgebiet();

      for (Gebiet zielGebiet : quellGebiet.getZielGebieteBeiAufteilung(wahlDAO, gebietDAO)) {
        LOG.info("Speichere Stimmeingaben zusätzlich in Wahl: " + zielGebiet.getWahl() + ", fuer Gebiet: " + zielGebiet);
        Ergebniseingang zielErgebniseingang = new Ergebniseingang();
        ergebniseingang.copyEntityAttributesUsingSetter(zielErgebniseingang);
        zielErgebniseingang.setEingangssequenznummer(null);
        zielErgebniseingang.setErgebnissequenznummer(null);
        zielErgebniseingang.setOverrideErgebniseingangsart(true); // ELECTKOM-27 und ELECT-8460 die Wahlen müssen nicht synchron sein in der Ergebniseingangsart

        zielErgebniseingang.setWahldurchfuehrungsgebiet(zielGebiet.castTo(Wahldurchfuehrungsgebiet.class).getEntity());

        saveErgebniseingang(zielErgebniseingang, null, false, getDefaultErgebniseingangValidatoren());

        // Werte wieder zurück
        zielErgebniseingang.setEingabeHashCode(ergebniseingang.getEingabeHashCode());
        zielErgebniseingang.setErgebniseingangsfehlerstatus(ergebniseingang.getErgebniseingangsfehlerstatus());
        zielErgebniseingang.setZeitstempel(ergebniseingang.getZeitstempel());
        zielErgebniseingang.setErfasser(zielErgebniseingang.getErfasser() + " (" + mandantNameFormatter.formatiereName(quellGebiet.getMandant()) + ")");

        Map<GruppeImGebiet, GruppeImGebiet> gigCache = new HashMap<>();

        Map<Liste, Liste> listenVonNachMap = new HashMap<>();

        for (Stimmeingabe stimmeingabe : ergebniseingang.getStimmeingabesByErgebniseingang()) {
          Stimmeingabe zielStimmeingabe;
          if (stimmeingabe.isInstanceOf(ZwischensummeEingabe.class)) {
            zielStimmeingabe = new ZwischensummeEingabe();
          } else {
            zielStimmeingabe = new Stimmeingabe();
          }
          stimmeingabe.copyEntityAttributesUsingSetter(zielStimmeingabe);
          zielStimmeingabe.setErgebniseingang(zielErgebniseingang);

          GruppeImGebiet zielGruppeImGebiet = findeGruppeImGebietInWahl(zielGebiet, stimmeingabe.getGruppeImGebiet(), gigCache);
          zielStimmeingabe.setGruppeImGebiet(zielGruppeImGebiet);
          zielStimmeingabe.setListenkandidatur(findeListenkandidatur(zielGruppeImGebiet, stimmeingabe.getListenkandidatur(), listenVonNachMap));
          save(zielStimmeingabe);

          for (Stimmeingabefehler stimmeingabefehler : stimmeingabe.getStimmeingabefehlersByStimmeingabe()) {
            Stimmeingabefehler zielStimmeingabefehler = new Stimmeingabefehler();
            stimmeingabefehler.copyEntityAttributesUsingSetter(zielStimmeingabefehler);
            zielStimmeingabefehler.setStimmeingabe(zielStimmeingabe);
            persistiere(zielStimmeingabefehler);

          }

        }
        postSave(zielErgebniseingang);
      }
    }
  }

  @Nullable
  private Listenkandidatur findeListenkandidatur(GruppeImGebiet zielGruppeImGebiet, Listenkandidatur listenkandidaturVon, Map<Liste, Liste> listenVonNachMap) {
    if (listenkandidaturVon == null) {
      return null;
    }

    // ELECT-8116
    Liste listeVon = listenkandidaturVon.getListe();
    Liste listeNach = listenVonNachMap.computeIfAbsent(listeVon, x -> {

      for (Liste liste : listeDAO.findByWahl(zielGruppeImGebiet.getWahl())) {
        if (liste.getGruppe().equals(zielGruppeImGebiet.getGruppe()) && liste.getGebiet().isInGebietshierarchieUebergeordnetesGebietVon(zielGruppeImGebiet.getGebiet())) {
          return liste;
        }
      }
      throw new IllegalStateException("Keine 'Zielliste' für " + listenkandidaturVon + ", " + listeVon + ", " + zielGruppeImGebiet + " gefunden");
    });

    Long relativePositionVon = listenkandidaturVon.getRelativePosition();
    for (Listenkandidatur listenkandidatur : listeNach.getListenkandidaturen()) {
      if (listenkandidatur.getRelativePosition().equals(relativePositionVon)) {
        return listenkandidatur;
      }
    }

    throw new IllegalStateException("Keine 'Ziellistenkandidatur' für " + listenkandidaturVon + ", " + listeVon + ", " + listeNach + ", " + relativePositionVon + " gefunden");
  }

  private GruppeImGebiet findeGruppeImGebietInWahl(Gebiet zielgebiet, GruppeImGebiet gruppeImGebiet, Map<GruppeImGebiet, GruppeImGebiet> gigCache) {
    if (!gigCache.containsKey(gruppeImGebiet)) {
      try {
        GruppeImGebiet result = gruppeImGebietDAO.findByUK(new GruppeImGebietUK(zielgebiet.getGebietUK(), new GruppeUK(gruppeImGebiet.getGruppe().getSchluessel(), zielgebiet.getWahl().getWahlUK())));
        gigCache.put(gruppeImGebiet, result);
      } catch (NoResultException nre) {
        throw new ElectCommonException("mandant.aufteilen.stimmen.uebernehmen.gig.fehlt", mandantNameFormatter.formatiereName(zielgebiet.getMandant()),
            gebietNummerFormatter.getDarstellung(zielgebiet), gruppeImGebiet.getGruppe().getSchluessel());
      }
    }
    return gigCache.get(gruppeImGebiet);
  }
}
