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

import static de.ivu.elect.business.system.entity.SystemLogQuelle.PERSONENPRUEFUNG;
import static org.apache.commons.lang3.StringUtils.leftPad;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

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

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

import de.ivu.elect.business.konfiguration.entity.ElectKonfigurationWerte;
import de.ivu.elect.business.konfiguration.entity.KonfigurationLoader;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.person.boundary.PersonendatenDAO;
import de.ivu.elect.business.person.entity.PersonStatus;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.personenpruefung.entity.PersonenKonflikt;
import de.ivu.elect.business.personenpruefung.entity.PersonenKonfliktArt;
import de.ivu.elect.business.system.boundary.SystemLogWriter;

@Stateless
public class PersonenPruefer {

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

  @Inject
  protected transient PersonendatenDAO personendatenDAO;

  @Inject
  protected transient SystemLogWriter systemLogWriter;

  @Inject
  protected transient KonfigurationLoader konfigurationLoader;

  @Inject
  protected transient PersonenPruefProzess personenPruefProzess;

  public PersonenPruefer() {
    super();
  }

  public boolean isZuPruefendePersonenVorhanden() {
    return !queryPersonendatenUngeprueft(1).isEmpty();
  }

  public void pruefe() {
    try {
      int maxResultSize = konfigurationLoader.resolveIntegerValue(ElectKonfigurationWerte.PERSONEN_PRUEFUNG_MAX_ANZAHL_PRUEFUNGEN_PRO_TRANSAKTION);

      for (Entry<Long, Collection<Personendaten>> personendatenUngeprueftPerMandantNummer : collectPersonendatenUngeprueftPerMandantNummer(maxResultSize).entrySet()) {
        pruefeIntern(personendatenUngeprueftPerMandantNummer.getKey(), personendatenUngeprueftPerMandantNummer.getValue());
      }

      personendatenDAO.flush();
      personendatenDAO.clear();
    } catch (RuntimeException ex) {
      systemLogWriter.writeErrorLogMessageNewTransaction("Fehler bei der Hintergrundprüfung für Personen", ex, PERSONENPRUEFUNG);

      throw ex;
    }
  }

  // ELECT-8415
  private Map<Long, Collection<Personendaten>> collectPersonendatenUngeprueftPerMandantNummer(int maxResultSize) {
    Map<Long, Collection<Personendaten>> result = new HashMap<>();

    for (Personendaten personendaten : findeZuPruefendePersonen(maxResultSize)) {
      Long mandantNummer = -1L;
      Mandant mandant = personendaten.getMandant();

      if (mandant == null || mandant.getNummer() == null) {
        LOG.warn("Fehlender Mandant bei der Hintergrundprüfung für Personen [personendaten = " + personendaten.getId() + ", mandant = " + mandant + ", wahl = " + personendaten.getWahl()
            + ", wahltermin = " + personendaten.getWahltermin() + "]");
      } else {
        mandantNummer = mandant.getNummer();
      }

      if (!result.containsKey(mandantNummer)) {
        result.put(mandantNummer, new ArrayList<>());
      }

      result.get(mandantNummer).add(personendaten);
    }

    return result;
  }

  protected Collection<Personendaten> findeZuPruefendePersonen(int maxResultSize) {
    return queryPersonendatenUngeprueft(maxResultSize);
  }

  protected Collection<Personendaten> queryPersonendatenUngeprueft(int maxResultSize) {
    return personendatenDAO.findByPersonStatus(PersonStatus.UNGEPRUEFT, maxResultSize);
  }

  private void pruefeIntern(Long mandantNummer, Collection<Personendaten> personendatenUngeprueft) {
    log(mandantNummer, "Hintergrundprüfung für Personen - start");

    List<PersonenPruefSchritt> pruefSchritte = personenPruefProzess.getPruefSchritte();
    Set<Personendaten> personendatenPotentiellGeeignet = new HashSet<>(personendatenUngeprueft);

    log(mandantNummer, "Personen ungeprüft: " + personendatenPotentiellGeeignet.size());

    for (int i = 0; i < pruefSchritte.size(); i++) {
      PersonenPruefSchritt pruefSchritt = pruefSchritte.get(i);
      int anzahlPersonendatenPotentiellGeeignetVorPruefSchritt = personendatenPotentiellGeeignet.size();

      log(mandantNummer, leftPad(i + 1 + ".", 3, "0") + " Prüfschritt [impl = " + pruefSchritt.getClass().getSimpleName() + ", konfliktArt = " + pruefSchritt.getPersonenKonfliktArt() + ", instant = "
          + pruefSchritt.isInstantPersonenPruefSchritt() + "]");

      invokePruefSchrittAndRemoveAllPersonendatenMitRelevantenKonflikten(pruefSchritt, personendatenPotentiellGeeignet);

      log(mandantNummer, "\tPersonen mit Konflikten: " + (anzahlPersonendatenPotentiellGeeignetVorPruefSchritt - personendatenPotentiellGeeignet.size()));
    }

    log(mandantNummer, "Personen geeignet: " + personendatenPotentiellGeeignet.size());

    updatePersonStatus(personendatenPotentiellGeeignet, PersonStatus.GEEIGNET);

    log(mandantNummer, "Hintergrundprüfung für Personen - done");
  }

  void invokePruefSchrittAndRemoveAllPersonendatenMitRelevantenKonflikten(PersonenPruefSchritt pruefSchritt, Set<Personendaten> personendatenPotentiellGeeignet) {
    PersonenKonfliktArt konfliktArt = pruefSchritt.getPersonenKonfliktArt();
    Map<Personendaten, PersonenKonflikt> konfliktPerPersonendaten = pruefSchritt.pruefe(personendatenPotentiellGeeignet);

    for (Entry<Personendaten, PersonenKonflikt> entry : konfliktPerPersonendaten.entrySet()) {
      PersonenKonflikt konflikt = entry.getValue();

      if (isRelevanterKonflikt(konfliktArt, konflikt)) {
        Personendaten personendatenMitRelevantemKonflikt = entry.getKey();

        personendatenPotentiellGeeignet.remove(personendatenMitRelevantemKonflikt);
      }
    }
  }

  private boolean isRelevanterKonflikt(PersonenKonfliktArt konfliktArt, PersonenKonflikt konflikt) {
    return konfliktArt.equals(konflikt.getPersonenKonfliktArt()) && !konflikt.isKonfliktStatusIrrelevant();
  }

  private void updatePersonStatus(Collection<Personendaten> personendaten, PersonStatus personStatus) {
    for (Personendaten person : personendaten) {
      person.setPersonStatus(personStatus);
    }
  }

  private void log(Long mandantNummer, String message) {
    LOG.info("Mandant " + mandantNummer + " - " + message);
  }
}
