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

import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

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

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gruppe.boundary.GruppeDAO;
import de.ivu.elect.business.gruppe.boundary.PolitischeGruppeDAO;
import de.ivu.elect.business.gruppe.boundary.PolitischeGruppeFactory;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.NlGruppeReihenfolgeBerechnenEvent;
import de.ivu.elect.business.gruppe.entity.NlPolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
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.kandidatur.entity.NlListenkandidatur;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.system.entity.SystemLogQuelle;
import de.ivu.elect.business.wahlvorschlag.entity.AbstractWahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.NlListe;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagPK;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.presentation.wahlvorschlag.NlListeStelInformationChangeEvent;

@Stateless
public class NlBlancoListeService {

  @Inject
  private transient ListeDAO listeDAO;

  @Inject
  private transient SystemLogWriter systemLogWriter;

  @Inject
  private transient Messages messages;

  @Inject
  private transient GruppeDAO gruppeDAO;

  @Inject
  private transient WahlvorschlagCommonEdit wahlvorschlagCommonEdit;

  @Inject
  private transient PolitischeGruppeFactory politischeGruppeFactory;

  @Inject
  private transient PolitischeGruppeDAO politischeGruppeDAO;

  @Inject
  private transient NlListeStelInformationChangeEvent nlListeStelInformationChangeEvent;

  @Inject
  private transient NlGruppeReihenfolgeBerechnenEvent nlGruppeReihenfolgeBerechnenEvent;

  @Inject
  private transient GruppeImGebietDAO gruppeImGebietDAO;

  public NlBlancoListeService() {
    super();
  }

  /**
   * ELECTVAPP-1696
   */
  public boolean repariereNameBlancoListe(Gruppe gruppe) {
    boolean result = false;
    NlPolitischeGruppe nlPolitischeGruppe = new NlPolitischeGruppe(gruppe);

    if (nlPolitischeGruppe.isBlancoListe()) {
      Liste liste = findeErsteZugelasseneListe(gruppe);

      if (liste != null) {
        Listenkandidatur listenkandidatur = findeErsteZugelasseneListenkandidatur(liste);

        if (listenkandidatur != null) {
          String nameBlancoListe = nlPolitischeGruppe.formatNameBlancoListe(Optional.of(new NlListenkandidatur(listenkandidatur)));
          String nameBisher = nlPolitischeGruppe.getName();

          if (!StringUtils.equals(nameBisher, nameBlancoListe)) {
            result = true;
            systemLogWriter.writeInfoLogMessageFuerMandant(gruppe, messages.getMessageForMessageKey("blanco.liste.umbenannt", nameBisher, nameBlancoListe), SystemLogQuelle.SYSTEM);

            nlPolitischeGruppe.setBlancoListe(nameBlancoListe);

            for (Liste listeDerGruppe : listeDAO.findByGruppe(gruppe)) {
              NlListe.setBlancoListe(liste, nameBlancoListe); // ELECT-14164
              GruppeImGebiet gruppeImGebiet = listeDerGruppe.getGruppeImGebiet();
              gruppeImGebiet.setKurzname(nameBlancoListe);
              gruppeImGebiet.setName(nameBlancoListe);

              // ELECT-14616
              listeDerGruppe.setKennwort(nameBlancoListe);
              listeDerGruppe.setKurzname(nameBlancoListe);
            }

            // ELECT-14616
            for (GruppeImGebiet gruppeImGebiet : gruppeImGebietDAO.findByGruppe(gruppe)) {
              gruppeImGebiet.setKurzname(nameBlancoListe);
              gruppeImGebiet.setName(nameBlancoListe);
            }
          }
        }
      }
    }

    if (result) {
      nlGruppeReihenfolgeBerechnenEvent.sendGruppeReihenfolgeBerechnenEvent(); // ELECT-14549
      nlListeStelInformationChangeEvent.sendEvent(); // ELECT-14549
    }
    return result;
  }

  public boolean repair(Gruppe gruppeNotAttached) {
    Gruppe gruppe = gruppeDAO.attach(gruppeNotAttached);
    boolean result = repariereNameBlancoListe(gruppe);

    Map<String, List<WahlvorschlagPK>> namenMap = bestimmeVerschiedeneNamenInnerhalbBlancoListe(gruppe);

    if (namenMap.size() > 1) {

      boolean first = true;
      for (Map.Entry<String, List<WahlvorschlagPK>> entry : namenMap.entrySet()) {
        if (first) {
          first = false;
        } else {
          PolitischeGruppe neueGruppe = null;
          for (WahlvorschlagPK wahlvorschlagPK : entry.getValue()) {
            result = true;
            if (neueGruppe == null) {
              String neuerName = entry.getKey();
              neueGruppe = neueGruppe(wahlvorschlagPK, neuerName);
              listeDAO.flush();
            } else {
              // ELECT-14541
              wahlvorschlagCommonEdit.gruppeWechseln(listeDAO.findByPrimaryKey(wahlvorschlagPK), neueGruppe);
            }
          }
        }
      }
    }
    if (result) {
      nlGruppeReihenfolgeBerechnenEvent.sendGruppeReihenfolgeBerechnenEvent(); // ELECT-14549
      nlListeStelInformationChangeEvent.sendEvent(); // ELECT-14549
    }

    return result;
  }

  public PolitischeGruppe neueGruppe(WahlvorschlagPK wahlvorschlagPK, String neuerNameVorgegeben) {
    Liste liste = listeDAO.findByPrimaryKey(wahlvorschlagPK);

    PolitischeGruppe gruppeBisher = liste.getGruppe();
    PolitischeGruppe neueGruppe = politischeGruppeFactory.create();
    gruppeBisher.copyEntityAttributesUsingSetter(neueGruppe);
    neueGruppe.setSchluessel(UUID.randomUUID().toString());

    String neuerName = neuerNameVorgegeben;
    if (StringUtils.isBlank(neuerName)) {
      Listenkandidatur listenkandidatur = findeErsteZugelasseneListenkandidatur(liste);
      if (listenkandidatur != null) {
        neuerName = new NlPolitischeGruppe(neueGruppe).formatNameBlancoListe(Optional.of(new NlListenkandidatur(listenkandidatur)));
      }
    }
    if (StringUtils.isBlank(neuerName)) {
      neuerName = "?";
    }
    neueGruppe.setName(neuerName);
    neueGruppe.setKurzname(neuerName);
    neueGruppe.setWahl(gruppeBisher.getWahl());
    politischeGruppeDAO.persist(neueGruppe);
    wahlvorschlagCommonEdit.gruppeWechseln(liste, neueGruppe);

    nlGruppeReihenfolgeBerechnenEvent.sendGruppeReihenfolgeBerechnenEvent(); // ELECT-14549
    nlListeStelInformationChangeEvent.sendEvent(); // ELECT-14549

    return neueGruppe;
  }

  public Map<String, List<WahlvorschlagPK>> bestimmeVerschiedeneNamenInnerhalbBlancoListe(Gruppe gruppe) {
    Map<String, List<WahlvorschlagPK>> result = new LinkedHashMap<>();

    NlPolitischeGruppe nlPolitischeGruppe = new NlPolitischeGruppe(gruppe);

    for (Liste liste : collectBlancoListen(gruppe)) {
      Listenkandidatur listenkandidatur = findeErsteZugelasseneListenkandidatur(liste);

      if (listenkandidatur != null) {
        String nameBlancoListe = nlPolitischeGruppe.formatNameBlancoListe(Optional.of(new NlListenkandidatur(listenkandidatur)));

        result.computeIfAbsent(nameBlancoListe, x -> new ArrayList<>()).add(liste.getPk());
      }
    }

    return result;
  }

  public List<Liste> collectBlancoListen(Gruppe gruppe) {
    NlPolitischeGruppe nlPolitischeGruppe = new NlPolitischeGruppe(gruppe);

    if (nlPolitischeGruppe.isBlancoListe()) {
      return listeDAO.findByGruppe(gruppe).stream().filter(AbstractWahlvorschlag::isZugelassen)
          .sorted((one, another) -> GebietByBreadthFirstComparator.compareNummer(one.getGebiet(), another.getGebiet())).collect(toList());
    }

    return emptyList();
  }

  @Nullable
  private Listenkandidatur findeErsteZugelasseneListenkandidatur(Liste liste) {
    return liste.getListenkandidaturenSortiertNachRelativerPosition().stream().filter(AbstractKandidatur::isZugelassen).findFirst().orElse(null);
  }

  @Nullable
  private Liste findeErsteZugelasseneListe(Gruppe gruppe) {
    return listeDAO.findByGruppe(gruppe).stream() //
        .filter(AbstractWahlvorschlag::isZugelassen) //
        .min((u, v) -> GebietByBreadthFirstComparator.compareNummer(u.getGebiet(), v.getGebiet())) //
        .orElse(null);
  }
}
