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

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

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.gruppeimgebiet.boundary.GruppeImGebietDAO;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
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.boundary.ReferendumOptionDAO;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.ReferendumOption;
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 ReferendumOptionDAO referendumOptionDAO;

  @Inject
  private transient MandantSupport mandantSupport;

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

      for (var zielGebiet : quellGebiet.getZielGebieteBeiAufteilung(wahlDAO, gebietDAO)) {
        LOG.info("Speichere Stimmeingaben zusätzlich in Wahl: " + zielGebiet.getWahl() + ", fuer Gebiet: " + zielGebiet);
        var 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<>();
        Map<ReferendumOption, ReferendumOption> referendumOptionVonNachMap = new HashMap<>();

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

          var zielGruppeImGebiet = findeGruppeImGebietInWahl(zielGebiet, stimmeingabe.getGruppeImGebiet(), gigCache, gigInZielGebiet);
          zielStimmeingabe.setGruppeImGebiet(zielGruppeImGebiet);
          zielStimmeingabe.setListenkandidatur(findeListenkandidatur(zielGruppeImGebiet, stimmeingabe.getListenkandidatur(), listenVonNachMap));
          zielStimmeingabe.setReferendumOption(findeReferendumOption(zielGruppeImGebiet, stimmeingabe.getReferendumOption(), referendumOptionVonNachMap));

          save(zielStimmeingabe);

          for (var stimmeingabefehler : stimmeingabe.getStimmeingabefehlersByStimmeingabe()) {
            var 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
    var listeVon = listenkandidaturVon.getListe();
    var listeNach = listenVonNachMap.computeIfAbsent(listeVon, x -> findeListe(zielGruppeImGebiet, listenkandidaturVon, listeVon));

    var listenkandidaturNachRelativerPosition = getListenkandidaturNachRelativerPosition(listeNach);
    var relativePositionVon = listenkandidaturVon.getRelativePosition();
    var result = listenkandidaturNachRelativerPosition.get(relativePositionVon);
    if (result == null) {
      throw new IllegalStateException(
          "Keine 'Ziellistenkandidatur' für " + listenkandidaturVon + ", " + listeVon + ", " + listeNach + ", " + relativePositionVon + " gefunden");

    }
    return result;
  }

  @Nonnull
  private static Map<Long, Listenkandidatur> getListenkandidaturNachRelativerPosition(Liste listeNach) {
    return (Map<Long, Listenkandidatur>) listeNach.getTransientContext()
        .computeIfAbsent("listenkandidaturNachRelativerPosition", x -> mappeListenkandidatenNachPosition(listeNach));
  }

  @Nonnull
  private static Map<Long, Listenkandidatur> mappeListenkandidatenNachPosition(Liste listeNach) {
    return listeNach.getListenkandidaturen().stream()
        .collect(Collectors.toMap(Listenkandidatur::getRelativePosition, listenkandidatur -> listenkandidatur, (a, b) -> b));
  }

  @Nonnull
  private Liste findeListe(GruppeImGebiet zielGruppeImGebiet, Listenkandidatur listenkandidaturVon, Liste listeVon) {

    var moeglicheListen = listeDAO.findByWahl(zielGruppeImGebiet.getWahl()).stream()
        .filter(liste -> liste.getGruppe() != null && liste.getGruppe().equals(zielGruppeImGebiet.getGruppe()) && liste.getGebiet()
            .isInGebietshierarchieUebergeordnetesGebietVon(zielGruppeImGebiet.getGebiet())).collect(Collectors.toList());

    if (moeglicheListen.isEmpty()) {
      throw new IllegalStateException("Keine 'Zielliste' für " + listenkandidaturVon + ", " + listeVon + ", " + zielGruppeImGebiet + " gefunden");
    }

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

    var result = moeglicheListen.get(0);

    // ELECT-15744 unterste Liste nehmen
    for (var liste : moeglicheListen) {
      if (liste.getGebiet().isUntergebietVon(result.getGebiet())) {
        result = liste;
      }
    }
    return result;

  }

  @Nullable
  private ReferendumOption findeReferendumOption(GruppeImGebiet zielGruppeImGebiet, ReferendumOption referendumOption, Map<ReferendumOption, ReferendumOption> referendumOptionVonNachMap) {
    if (referendumOption == null) {
      return null;
    }

    // ELECT-15136
    return referendumOptionVonNachMap.computeIfAbsent(referendumOption, x -> {

      for (var zielReferendumOption : referendumOptionDAO.findByWahl(zielGruppeImGebiet.getWahl())) {
        if (zielReferendumOption.getPosition()
            .equals(referendumOption.getPosition()) && zielReferendumOption.getGruppe() != null && zielReferendumOption.getGruppe()
            .equals(zielGruppeImGebiet.getGruppe()) && zielReferendumOption.getGebiet()
            .isInGebietshierarchieUebergeordnetesGebietVon(zielGruppeImGebiet.getGebiet())) {
          return zielReferendumOption;
        }
      }
      throw new IllegalStateException("Keine 'zielReferendumOption' für " + referendumOption + ", " + zielGruppeImGebiet + " gefunden");
    });

  }

  private GruppeImGebiet findeGruppeImGebietInWahl(Gebiet zielgebiet, GruppeImGebiet gruppeImGebiet, Map<GruppeImGebiet, GruppeImGebiet> gigCache, List<GruppeImGebiet> gigInZielGebiet) {
    if (!gigCache.containsKey(gruppeImGebiet)) {
      gigInZielGebiet.stream().filter(
          gigInGebiet -> gigInGebiet.getGruppe().getSchluessel().equals(gruppeImGebiet.getGruppe().getSchluessel()) && zielgebiet.equals(
              gigInGebiet.getGebiet())).findFirst().ifPresent(gigInGebiet -> gigCache.put(gruppeImGebiet, gigInGebiet));
      if (!gigCache.containsKey(gruppeImGebiet)) {
        throw new ElectCommonException("mandant.aufteilen.stimmen.uebernehmen.gig.fehlt", mandantNameFormatter.formatiereName(zielgebiet.getMandant()),
            gebietNummerFormatter.getDarstellung(zielgebiet), gruppeImGebiet.getGruppe().getSchluessel());
      }
    }
    return gigCache.get(gruppeImGebiet);
  }
}
