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

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

import javax.ejb.Stateless;
import javax.inject.Inject;

import de.ivu.elect.business.benutzerverwaltung.entity.Rolle;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.control.GebietCreate;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietTK;
import de.ivu.elect.business.gebietsbaum.entity.GebietUebernehmenContext;
import de.ivu.elect.business.gebietsbaum.entity.GebietUebernehmenParameter;
import de.ivu.elect.business.gebietsbaum.entity.GebietUebernehmenResult;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.wahl.boundary.WahlDAO;
import de.ivu.elect.business.wahl.entity.Wahl;


import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;

@Stateless
public class GebietUebernehmen {

  @Inject
  private transient WahlDAO wahlDAO;

  @Inject
  private transient GebietsartDAO gebietsartDAO;

  @Inject
  private transient GebietDAO gebietDAO;

  @Inject
  private transient GebietFactory gebietFactory;

  @Inject
  private transient GebietCreate gebietCreate;

  @Inject
  private transient GebietEdit gebietEdit;

  public GebietUebernehmen() {
    super();
  }

  public GebietUebernehmenResult save(GebietUebernehmenParameter gebietUebernehmenParameter) {
    if (gebietUebernehmenParameter.getWahlQuelle() == null || gebietUebernehmenParameter.getWahlZiel() == null || gebietUebernehmenParameter.getGebietsartenZuUebernehmen().isEmpty()) {
      return GebietUebernehmenResult.NICHTS_UEBRNOMMEN;
    }

    Wahl quelle = wahlDAO.attach(gebietUebernehmenParameter.getWahlQuelle());
    Wahl ziel = wahlDAO.attach(gebietUebernehmenParameter.getWahlZiel());

    Set<Gebietsart> gebietsarten = new HashSet<>(gebietsartDAO.attachAll(gebietUebernehmenParameter.getGebietsartenZuUebernehmen()));

    List<Gebiet> gebieteQuelle = gebietDAO.findByWahl(quelle);
    List<Gebiet> gebieteZiel = gebietDAO.findByWahl(ziel);

    GebietUebernehmenContext uebernehmenContext = new GebietUebernehmenContext(gebietUebernehmenParameter.isExterneIdFuerErfassungsgebieteUebernehmen(),
        gebietUebernehmenParameter.isAnzahlenSitzeStimmenUebernehmen(), gebietUebernehmenParameter.isRollenUebernehmen(),
        gebietUebernehmenParameter.isErfassungsstatusUebernehmen(), gebietUebernehmenParameter.isAwerteUebernehmen());

    return uebernehmeGebiete(ziel, gebieteQuelle, gebieteZiel, gebietsarten, gebietUebernehmenParameter.isGebieteAktualisierenWennSchonVorhanden(),
        gebietUebernehmenParameter.isUeberzaehligeGebieteLoeschen(), uebernehmenContext);
  }

  private GebietUebernehmenResult uebernehmeGebiete(Wahl zielWahl, List<Gebiet> gebieteQuelle, List<Gebiet> gebieteZiel, Set<Gebietsart> gebietsarten, boolean aktualisieren,
                                                    boolean ueberzaehligeLoeschen, GebietUebernehmenContext uebernehmenContext) {
    Map<GebietTK, Gebiet> gebieteNachTKZiel = gebieteZiel.stream().collect(toMap(Gebiet::getGebietTK, identity()));

    Map<GebietTK, Gebiet> zuLoeschendeGebiete = new HashMap<>();
    if (ueberzaehligeLoeschen) {
      zuLoeschendeGebiete = gebieteZiel.stream().filter(zielGebiet -> gebietsarten.contains(zielGebiet.getGebietsart()))
          .collect(toMap(Gebiet::getGebietTK, zielGebiet -> zielGebiet, (a, b) -> b));
    }

    Gebiet wurzelgebiet = null;

    Map<Gebietsart, AtomicLong> anzahlUebernommenerGebiete = gebietsarten.stream().collect(toMap(identity(), i -> new AtomicLong(0L)));
    Map<Gebietsart, AtomicLong> anzahlNichtUebernommenerGebiete = gebietsarten.stream().collect(toMap(identity(), i -> new AtomicLong(0L)));
    Map<Gebietsart, AtomicLong> anzahlBereitsVorhandenerGebiete = gebietsarten.stream().collect(toMap(identity(), i -> new AtomicLong(0L)));
    Map<Gebietsart, AtomicLong> anzahlAktualisierterGebiete = gebietsarten.stream().collect(toMap(identity(), i -> new AtomicLong(0L)));
    Map<Gebietsart, AtomicLong> anzahlGeloeschterGebiete = gebietsarten.stream().collect(toMap(identity(), i -> new AtomicLong(0L)));

    boolean wurzelgebietAngelegt = false;
    Map<String, Object> postCreateContext = createPostCreateContext(zielWahl);
    gebieteQuelle.sort(new GebietByBreadthFirstComparator());

    for (Gebiet quelle : gebieteQuelle) {
      if (gebietsarten.contains(quelle.getGebietsart())) {
        zuLoeschendeGebiete.remove(quelle.getGebietTK());

        GebietTK tk = quelle.getGebietTK();

        if (gebieteNachTKZiel.containsKey(tk)) {
          if (aktualisieren) {
            Gebiet bisherigesGebiet = gebieteNachTKZiel.get(tk);
            aktualisiere(bisherigesGebiet, quelle, uebernehmenContext);
            anzahlAktualisierterGebiete.get(quelle.getGebietsart()).addAndGet(1L);
          } else {
            anzahlBereitsVorhandenerGebiete.get(quelle.getGebietsart()).addAndGet(1L);
          }
        } else {
          Gebiet uebergeordnetesGebietZiel = findeUebergeordnetesZielGebiet(quelle, gebieteNachTKZiel);

          if (quelle.getUebergeordnetesGebiet() != null && uebergeordnetesGebietZiel == null) {
            // kein übergordnetes Gebiet gefunden und nicht wurzel => kann nicht kopiert werden
            anzahlNichtUebernommenerGebiete.get(quelle.getGebietsart()).addAndGet(1L);
          } else {
            if (quelle.getUebergeordnetesGebiet() == null && !gebieteNachTKZiel.isEmpty()) {
              if (aktualisieren) {
                Gebiet bisherigesWurzelgebiet = findeWurzelgebiet(gebieteNachTKZiel);
                if (bisherigesWurzelgebiet != null && Objects.equals(bisherigesWurzelgebiet.getGebietsart(), quelle.getGebietsart())) {
                  // nur die Nummer stimmt nicht => aktualisieren

                  // tk ändert sich...
                  gebieteNachTKZiel.remove(bisherigesWurzelgebiet.getGebietTK());
                  aktualisiere(bisherigesWurzelgebiet, quelle, uebernehmenContext);
                  gebieteNachTKZiel.put(bisherigesWurzelgebiet.getGebietTK(), bisherigesWurzelgebiet);

                  anzahlAktualisierterGebiete.get(quelle.getGebietsart()).addAndGet(1L);
                } else {
                  // Gebietsart des Wurzelgebietes stimmt nicht => nicht aktualisieren
                  anzahlNichtUebernommenerGebiete.get(quelle.getGebietsart()).addAndGet(1L);
                }
              } else {
                // ELECTKOM-33 Wurzelgebiet kann nicht übenommen werden, wenn schon Gebiete (und damit ein Wurzelgebiet) vorhanden sind
                anzahlNichtUebernommenerGebiete.get(quelle.getGebietsart()).addAndGet(1L);
              }
            } else {
              Gebiet neueZielGebiet = gebietFactory.create();
              neueZielGebiet.setUebergeordnetesGebiet(uebergeordnetesGebietZiel);
              kopiereGebietseigenschaftenFuerWahl(quelle, neueZielGebiet, zielWahl, uebernehmenContext);
              persistiereNeuesGebiet(neueZielGebiet, gebieteNachTKZiel);

              if (quelle.getUebergeordnetesGebiet() == null) {
                wurzelgebietAngelegt = true;
                wurzelgebiet = neueZielGebiet;
              }

              postCreateGebiet(postCreateContext, neueZielGebiet, wurzelgebietAngelegt);

              anzahlUebernommenerGebiete.get(quelle.getGebietsart()).addAndGet(1L);
            }
          }
        }
      }
    }
    if (!zuLoeschendeGebiete.isEmpty()) {
      gebietDAO.flush();
      gebietDAO.clear();
      for (Gebiet ueberzaehligesGebietNotAttached : zuLoeschendeGebiete.values()) {
        Gebiet ueberzaehligesGebiet = gebietDAO.attach(ueberzaehligesGebietNotAttached);
        anzahlGeloeschterGebiete.get(ueberzaehligesGebiet.getGebietsart()).addAndGet(1L);
        gebietEdit.delete(ueberzaehligesGebiet);
      }
    }

    return new GebietUebernehmenResult(gebietDAO.attach(wurzelgebiet), filter(anzahlUebernommenerGebiete), filter(anzahlNichtUebernommenerGebiete),
        filter(anzahlBereitsVorhandenerGebiete),
        filter(anzahlAktualisierterGebiete), filter(anzahlGeloeschterGebiete));
  }

  private Gebiet findeWurzelgebiet(Map<GebietTK, Gebiet> gebieteNachTKZiel) {
    return gebieteNachTKZiel.values().stream().filter(gebiet -> gebiet.getUebergeordnetesGebiet() == null).findFirst().orElse(null);
  }

  private void aktualisiere(Gebiet ziel, Gebiet quelle, GebietUebernehmenContext uebernehmenContext) {
    kopiereGebiet(quelle, ziel, uebernehmenContext);

    ziel.setEinsehenRolle(quelle.getEinsehenRolle());
    ziel.setBearbeitenRolle(quelle.getBearbeitenRolle());
    ziel.setVerantwortlicherMandant(quelle.getVerantwortlicherMandant());
  }

  protected Map<String, Object> createPostCreateContext(Wahl zielWahl) {
    return new HashMap<>();
  }

  protected void postCreateGebiet(Map<String, Object> postCreateContext, Gebiet neueZielGebiet, boolean wurzelgebietAngelegt) {
    // nichts zu tun
  }

  private Map<Gebietsart, Long> filter(Map<Gebietsart, AtomicLong> quelle) {
    Map<Gebietsart, Long> result = new HashMap<>();

    quelle.entrySet().stream().filter(e -> e.getValue().longValue() > 0).forEach(e -> result.put(e.getKey(), e.getValue().longValue()));

    return result;
  }

  private Gebiet findeUebergeordnetesZielGebiet(Gebiet quelle, Map<GebietTK, Gebiet> gebieteNachTKZiel) {
    Gebiet uebergeordnetQuelle = quelle.getUebergeordnetesGebiet();

    if (uebergeordnetQuelle == null) {
      return null;
    }

    GebietTK tk = uebergeordnetQuelle.getGebietTK();

    if (gebieteNachTKZiel.containsKey(tk)) {
      return gebieteNachTKZiel.get(tk);
    }

    return findeUebergeordnetesZielGebiet(uebergeordnetQuelle, gebieteNachTKZiel);
  }

  private void persistiereNeuesGebiet(Gebiet neuesGebiet, Map<GebietTK, Gebiet> gebieteNachTKZiel) {
    gebietCreate.save(neuesGebiet); // ELECTKOM-490 und ELECT-8716 => nur save
    postPersistiereNeuesGebiet(neuesGebiet);
    gebieteNachTKZiel.put(neuesGebiet.getGebietTK(), neuesGebiet);
  }

  protected void postPersistiereNeuesGebiet(Gebiet neuesGebiet) {
    // nichts zu tun
  }

  protected void kopiereGebietseigenschaftenFuerWahl(Gebiet quelle, Gebiet ziel, Wahl zielWahl, GebietUebernehmenContext uebernehmenContext) {
    ziel.setGebietsart(quelle.getGebietsart());
    ziel.setWahl(zielWahl);
    ziel.setWahltermin(zielWahl.getWahltermin());

    kopierereGebietUnveraendert(quelle, ziel, uebernehmenContext);

    // ELECT-8543 nach unverändert, persistente Rollen bleiben
    ziel.setEinsehenRolle(quelle.getEinsehenRolle());
    ziel.setBearbeitenRolle(quelle.getBearbeitenRolle());
    ziel.setVerantwortlicherMandant(quelle.getVerantwortlicherMandant());

  }

  public void kopierereGebietUnveraendert(Gebiet quelle, Gebiet ziel, GebietUebernehmenContext uebernehmenContext) {
    kopiereGebiet(quelle, ziel, uebernehmenContext);

    ziel.setCustomDataXml(quelle.getCustomDataXml());
    ziel.getAnsprechpartner().setCustomDataXml(quelle.getAnsprechpartner().getCustomDataXml());
    ziel.getAnschrift().setCustomDataXml(quelle.getAnschrift().getCustomDataXml());

    if (uebernehmenContext.isRollenUebernehmen()) {
      // ELECT-8549
      if (quelle.getEinsehenRolle() != null) {
        Rolle einsehenRolle = new Rolle();
        einsehenRolle.setName(quelle.getEinsehenRolle().getName());
        ziel.setEinsehenRolle(einsehenRolle);
      }
      if (quelle.getBearbeitenRolle() != null) {
        Rolle bearbeitenRolle = new Rolle();
        bearbeitenRolle.setName(quelle.getBearbeitenRolle().getName());
        ziel.setBearbeitenRolle(bearbeitenRolle);
      }
    }
  }

  protected void kopiereGebiet(Gebiet quelle, Gebiet ziel, GebietUebernehmenContext uebernehmenContext) {
    String externeIdVorher = ziel.getExterneId();
    String externeIdSaltVorher = ziel.getExterneIdSalt();

    Long anzahlSitzeVorher = ziel.getAnzahlSitze();
    Long anzahlStimmenProWaehlerVorher = ziel.getAnzahlStimmenProWaehler();

    quelle.copyEntityAttributesUsingSetter(ziel);

    if (quelle.isErfassungsgebiet() && !uebernehmenContext.isExterneIdFuerErfassungsgebieteUebernehmen()) {
      // ELECT-9138
      ziel.setExterneId(externeIdVorher);
      ziel.setExterneIdSalt(externeIdSaltVorher);
    }

    if (!uebernehmenContext.isAnzahlenSitzeStimmenUebernehmen()) {
      ziel.setAnzahlSitze(anzahlSitzeVorher);
      ziel.setAnzahlStimmenProWaehler(anzahlStimmenProWaehlerVorher);
    }

    quelle.getAnschrift().copyEntityAttributesUsingSetter(ziel.getAnschrift());
    quelle.getAnsprechpartner().copyEntityAttributesUsingSetter(ziel.getAnsprechpartner());
  }
}
