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

import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

import javax.inject.Inject;

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

import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonenregisterAbgleichStatus;
import de.ivu.elect.business.personenpruefung.control.AbstractPersonenPruefSchritt;
import de.ivu.elect.business.personenpruefung.entity.PersonenKonflikt;
import de.ivu.elect.business.personenpruefung.entity.PersonenKonfliktArt;
import de.ivu.elect.business.personenpruefung.entity.PersonenKonfliktStatus;
import de.ivu.elect.business.personenregister.boundary.AbstractPersonenregisterSearchParameters;
import de.ivu.elect.business.personenregister.boundary.PersonendatenPersonenregisterPersonendatenMapperResolver;
import de.ivu.elect.business.personenregister.boundary.PersonendatenPersonenregisterPersonendatenMatcherResolver;
import de.ivu.elect.business.personenregister.boundary.Personenregister;
import de.ivu.elect.business.personenregister.boundary.PersonenregisterByOrdnungsmerkmalSearchParameters;
import de.ivu.elect.business.personenregister.boundary.PersonenregisterByPersonendatenSearchParameters;
import de.ivu.elect.business.personenregister.boundary.PersonenregisterSearchResult;
import de.ivu.elect.business.personenregister.boundary.PersonenregisterSwitch;
import de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMapper;
import de.ivu.elect.business.personenregister.control.PersonenregisterPersonendatenMatcher;
import de.ivu.elect.business.personenregister.entity.PersonenregisterEinwohnerstatus;
import de.ivu.elect.business.personenregister.entity.PersonenregisterPersonendaten;
import de.ivu.elect.business.staat.boundary.StaatDAO;
import de.ivu.elect.business.staat.boundary.StaatFinder;
import de.ivu.elect.business.staat.entity.Staat;

public abstract class AbstractPersonenPersonenregisterAbgleichPruefSchritt extends AbstractPersonenPruefSchritt {

  @Inject
  private transient PersonenregisterSwitch personenregisterSwitch;

  @Inject
  private transient PersonenregisterByOrdnungsmerkmalSearchParameters personenregisterByOrdnungsmerkmalSearchParameters;

  @Inject
  private transient PersonenregisterByPersonendatenSearchParameters personenregisterByPersonendatenSearchParameters;

  @Inject
  private transient PersonendatenPersonenregisterPersonendatenMatcherResolver personendatenPersonenregisterPersonendatenMatcherResolver;

  @Inject
  private transient PersonendatenPersonenregisterPersonendatenMapperResolver personendatenPersonenregisterPersonendatenMapperResolver;

  @Inject
  private transient StaatDAO staatDAO;

  @Inject
  private transient StaatFinder staatFinder;

  private boolean personenregisterErreichbar;

  private final Logger log;

  protected AbstractPersonenPersonenregisterAbgleichPruefSchritt() {
    super();

    this.log = LoggerFactory.getLogger(getClass());
  }

  @Override
  public boolean isInstantPersonenPruefSchritt() {
    return false;
  }

  @Override
  public PersonenKonfliktArt getPersonenKonfliktArt() {
    return PersonenKonfliktArt.ABGLEICH_FEHLGESCHLAGEN;
  }

  @Override
  public PersonenKonfliktStatus getPersonenKonfliktStatus() {
    return PersonenKonfliktStatus.MANUELL_ZU_PRUEFEN;
  }

  private void initPersonenregisterErreichbar(Mandant mandant) {
    this.personenregisterErreichbar = getPersonenregister(mandant).isErreichbar(mandant);
  }

  protected boolean isPersonenregisterErreichbar() {
    return personenregisterErreichbar;
  }

  protected Personenregister< ? > getPersonenregister(Mandant mandant) {
    return personenregisterSwitch.getPersonenregister(mandant);
  }

  @Override
  public Map<Personendaten, PersonenKonflikt> pruefe(Collection<Personendaten> personendaten) {
    if (personendaten.isEmpty()) {
      return Collections.emptyMap();
    }

    initPersonenregisterErreichbar(personendaten.iterator().next().getMandant());

    return super.pruefe(personendaten);
  }

  @Override
  public boolean isRelevantPersonendatenInstance(Personendaten personendaten) {
    return super.isRelevantPersonendatenInstance(personendaten) && !PersonenregisterAbgleichStatus.KEIN_ABGLEICH_NOTWENDIG.equals(personendaten.getPersonenregisterAbgleichStatus());
  }

  @Override
  public boolean isPotentiellNichtGeeignet(Personendaten personendaten) {
    if (!isPersonenregisterAvailable(personendaten.getMandant()) || !isPersonenregisterAbgleichbar(personendaten)) {
      return false;
    }

    boolean result = !isPersonenregisterErreichbar() || !isPersonenregisterAutomatischUebernommen(personendaten, findUniquePersonenregisterPersonendatenOrNull(personendaten));

    updatePersonendaten(personendaten);

    return result;
  }

  protected void updatePersonendaten(Personendaten personendaten) {
    personendaten.setPersonenregisterAbgleichStatus(PersonenregisterAbgleichStatus.ABGLEICH_DURCHGEFUEHRT);
    personendaten.setPersonenregisterAbgleichZeitpunkt(new Date());
  }

  protected boolean isPersonenregisterAvailable(Mandant mandant) {
    return personenregisterSwitch.isPersonenregisterAvailable(mandant);
  }

  private boolean isPersonenregisterAbgleichbar(Personendaten personendaten) {
    // NOTE, dass eine als auswärtig (und damit als nicht im Personenregister auffindbar) markierte Personen nicht mehr mit dem Personenregister abgeglichen wird.
    return !Boolean.TRUE.equals(personendaten.getAuswaertig());
  }

  protected boolean isPersonenregisterAutomatischUebernommen(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    if (isAutomatischUebernehmbar(personendaten, personenregisterPersonendaten)) {
      return tryMapPersonendaten(personendaten, personenregisterPersonendaten);
    }

    return false;
  }

  private boolean isAutomatischUebernehmbar(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    return personenregisterPersonendaten != null && getMatcher(personendaten).isAutomatischUebernehmbar(personendaten, personenregisterPersonendaten);
  }

  protected PersonenregisterPersonendatenMatcher getMatcher(Personendaten personendaten) {
    return personendatenPersonenregisterPersonendatenMatcherResolver.getMatcher(personendaten);
  }

  private boolean tryMapPersonendaten(Personendaten personendaten, PersonenregisterPersonendaten personenregisterPersonendaten) {
    Collection<Staat> staatsangehoerigkeiten = new ArrayList<>();

    for (String personenregisterCodeStaatsangehoerigkeit : personenregisterPersonendaten.getStaatsangehoerigkeiten()) {
      Staat staatsangehoerigkeit = tryAttachStaatsangehoerigkeit(personenregisterCodeStaatsangehoerigkeit);

      // NOTE, dass, falls keine passende Staatsangehörigkeit gefunden wird, IMMER ein Konflikt angelegt wird!
      if (staatsangehoerigkeit == null) {
        return false;
      }

      staatsangehoerigkeiten.add(staatsangehoerigkeit);
    }

    getMapper(personendaten).mapAutomatischerAbgleich(personendaten, personenregisterPersonendaten, staatsangehoerigkeiten);

    return true;
  }

  protected Staat tryAttachStaatsangehoerigkeit(String codeStaatsangehoerigkeit) {
    return staatFinder.tryFindUniqueStaat(staatDAO, codeStaatsangehoerigkeit);
  }

  protected PersonenregisterPersonendatenMapper getMapper(Personendaten personendaten) {
    return personendatenPersonenregisterPersonendatenMapperResolver.getMapper(personendaten);
  }

  private PersonenregisterPersonendaten findUniquePersonenregisterPersonendatenOrNull(Personendaten personendaten) {
    Mandant mandant = personendaten.getMandant();

    Personenregister< ? > personenregister = getPersonenregister(mandant);

    if (personenregister.isSearchByOrdnungsmerkmalAutomatischSupported(mandant) && personendaten.hasOrdnungsmerkmal()) {
      PersonenregisterByOrdnungsmerkmalSearchParameters searchParameters = getPersonenregisterByOrdnungsmerkmalSearchParameters(personendaten);
      PersonenregisterSearchResult< ? > searchResult = personenregister.search(mandant, UUID.randomUUID(), searchParameters);

      if (!searchResult.hasException() && searchResult.hasUniquePersonenregisterPersonendaten()) {
        PersonenregisterPersonendaten personenregisterPersonendaten = searchResult.getUniquePersonenregisterPersonendaten();

        String ordnungsmerkmalInDb = personendaten.getOrdnungsmerkmal();
        String ordnungsmerkmalInPersonenregister = personenregisterPersonendaten.getOrdnungsmerkmal();

        if (isNotBlank(ordnungsmerkmalInPersonenregister) && !equalsIgnoreCase(ordnungsmerkmalInPersonenregister, ordnungsmerkmalInDb)) {
          log.warn("ELECTKOM-1899 - Ordnungsmerkmal in Personenregister weicht von Ordnungsmerkmal in DB ab - AUTOMATISCHE KORREKTUR [personendaten = {}, bisher = {}, neu = {}]",
              personendaten.getId(), ordnungsmerkmalInDb, ordnungsmerkmalInPersonenregister);

          // ELECTKOM-1899, falls per Ordnungsmerkmal gefunden, dann dieses nochmals setzen, um bspw. "schleichend" alte MESO-OEMs durch neue VOIS-UUIDs zu ersetzen...
          personendaten.setOrdnungsmerkmal(ordnungsmerkmalInPersonenregister);
        }

        return personenregisterPersonendaten;
      }
    }

    if (personenregister.isSearchByPersonendatenAutomatischSupported(mandant)) { // ELECT-12300, automatischer Fallback auf Personendaten...
      PersonenregisterByPersonendatenSearchParameters searchParameters = getPersonenregisterByPersonendatenSearchParameters(personendaten);
      PersonenregisterSearchResult< ? > searchResult = personenregister.search(mandant, UUID.randomUUID(), searchParameters);

      if (!searchResult.hasException() && searchResult.hasUniquePersonenregisterPersonendaten()) {
        return searchResult.getUniquePersonenregisterPersonendaten();
      }
    }

    return null; // NOTE, dass evtl. aufgetretene Exceptions (vermerkt im PersonenregisterSearchResult) nicht ausgewertet werden und beim Rückgabewert null IMMER ein Konflikt angelegt wird!
  }

  protected PersonenregisterByOrdnungsmerkmalSearchParameters getPersonenregisterByOrdnungsmerkmalSearchParameters(Personendaten personendaten) {
    initDefaultPersonenregisterSearchParameters(personenregisterByOrdnungsmerkmalSearchParameters, personendaten);

    personenregisterByOrdnungsmerkmalSearchParameters.setOrdnungsmerkmal(personendaten.getOrdnungsmerkmal());

    return personenregisterByOrdnungsmerkmalSearchParameters;
  }

  protected PersonenregisterByPersonendatenSearchParameters getPersonenregisterByPersonendatenSearchParameters(Personendaten personendaten) {
    initDefaultPersonenregisterSearchParameters(personenregisterByPersonendatenSearchParameters, personendaten);

    personenregisterByPersonendatenSearchParameters.setFamilienname(personendaten.getFamilienname());
    personenregisterByPersonendatenSearchParameters.setVorname(personendaten.getRufname());

    personenregisterByPersonendatenSearchParameters.setGeburtsdatum(personendaten.getGeburtsdatum());
    personenregisterByPersonendatenSearchParameters.setGeschlecht(personendaten.getGeschlecht());

    return personenregisterByPersonendatenSearchParameters;
  }

  private void initDefaultPersonenregisterSearchParameters(AbstractPersonenregisterSearchParameters personenregisterSearchParameters, Personendaten personendaten) {
    personenregisterSearchParameters.setWahltermin(personendaten.getWahltermin());
    personenregisterSearchParameters.setEinwohnerstatus(PersonenregisterEinwohnerstatus.AKTIV);
  }
}
