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

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.inject.Inject;

import de.ivu.elect.business.person.boundary.PersonenVerknuepfungDAO;
import de.ivu.elect.business.person.boundary.PersonendatenDAO;
import de.ivu.elect.business.person.entity.PersonStatus;
import de.ivu.elect.business.person.entity.PersonenVerknuepfung;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonenregisterAbgleichStatus;
import de.ivu.elect.business.person.entity.VerknuepfungsArt;
import de.ivu.elect.business.personenpruefung.boundary.PersonenKonfliktEdit;
import de.ivu.elect.business.personenpruefung.entity.PersonenKonfliktArt;

/*
 * NOTE, dass diese Klasse nur ein Zwischenschritt des Refactoring im Zuge von ELECT-2733 ist. Besser wäre es noch jeden
 * Prüfschritt (beziehungsweise dessen Matcher) zu fragen, ob sich ein relevantes Attribut geändert hat.
 */
public class PersonenPruefungVorbereiter implements Serializable {

  private static final long serialVersionUID = 1L;

  @Inject
  private transient PersonenVerknuepfungDAO personenVerknuepfungDAO;

  @Inject
  private transient PersonendatenDAO personendatenDAO;

  @Inject
  private transient PersonenKonfliktEdit personenKonfliktEdit;

  public PersonenPruefungVorbereiter() {
    super();
  }

  public void preparePersonenregisterAbgleichPruefSchritt(Personendaten personendaten) {
    Set<PersonenKonfliktArt> konfliktArten = new HashSet<>();

    addKonfliktArtAndPreparePersonendatenForPersonenregisterAbgleichPruefSchritt(konfliktArten, personendaten);

    deleteKonflikte(personendaten, konfliktArten);
  }

  public void addKonfliktArtAndPreparePersonendatenForPersonenregisterAbgleichPruefSchritt(Collection<PersonenKonfliktArt> konfliktArten, Personendaten personendaten) {
    // NOTE, dass bei einem erneuten Personenregister-Abgleich auch andere Prüfungen erneut durchgeführt werden müssen, da relevante
    // Informationen, bspw. die Staatsangehörigkeiten oder der Wahlrechtsausschlusss, im jeweiligen Personenregister hinterlegt sind!
    konfliktArten.add(PersonenKonfliktArt.ABGLEICH_FEHLGESCHLAGEN);
    konfliktArten.add(PersonenKonfliktArt.WAHLRECHTLICHE_PRUEFUNG_FEHLGESCHLAGEN);
    konfliktArten.add(PersonenKonfliktArt.MEHRSTAATIGKEIT_PRUEFUNG_FEHLGESCHLAGEN);

    personendaten.setPersonenregisterAbgleichStatus(PersonenregisterAbgleichStatus.ABGLEICH_NOTWENDIG);

    markPersonendatenUngeprueft(personendaten);
  }

  public void addKonfliktArtAndPreparePersonendatenForDublettenPruefSchritt(Collection<PersonenKonfliktArt> konfliktArten, Personendaten personendaten) {
    konfliktArten.add(PersonenKonfliktArt.POTENTIELLE_DUBLETTE);

    // NOTE, dass die Verknüpfungen nur gelöscht werden sollten, wenn sich dublettenrelevante Attribute geändert haben!
    deleteAllVerknuepfungenAndMarkVerknuepftePersonenUngeprueft(personendaten, false, getDublettenVerknuepfungArten(personendaten));

    markPersonendatenUngeprueft(personendaten);
  }

  protected Collection<VerknuepfungsArt> getDublettenVerknuepfungArten(Personendaten personendaten) {
    return emptyList();
  }

  /**
   * NOTE, dass nur VERKNÜPFTE Personen, nicht aber die übergebene Person selbst als ungeprüft markiert werden!
   *
   * (Dies wird beim Löschen der Personendaten benötigt...)
   */
  public void deleteAllVerknuepfungenAndMarkVerknuepftePersonenUngeprueft(Personendaten personendaten) {
    deleteAllVerknuepfungenAndMarkVerknuepftePersonenUngeprueft(personendaten, true, asList(VerknuepfungsArt.values()));
  }

  /**
   * NOTE, dass nur VERKNÜPFTE Personen, nicht aber die übergebene Person selbst als ungeprüft markiert werden!
   *
   * (Dies wird beim Löschen der Personendaten benötigt...)
   */
  protected void deleteAllVerknuepfungenAndMarkVerknuepftePersonenUngeprueft(Personendaten personendaten, boolean personendatenDeleted, Collection<VerknuepfungsArt> verknuepfungsArten) {
    Collection<PersonenVerknuepfung> personenVerknuepfungenDeleted = new HashSet<>();

    if (personendatenDeleted) { // ELECT-11067
      personenVerknuepfungenDeleted.addAll(findPersonenVerknuepfungenDeletedIfPersonendatenDeleted(personendaten));
    } else {
      personenVerknuepfungenDeleted.addAll(findPersonenVerknuepfungenDeletedIfPersonendatenEdited(personendaten, verknuepfungsArten));
    }

    for (PersonenVerknuepfung personenVerknuepfung : personenVerknuepfungenDeleted) {
      Personendaten person1 = personenVerknuepfung.getPerson1();
      Personendaten person2 = personenVerknuepfung.getPerson2();

      if (person1 != null && !personendaten.equals(person1)) {
        markPersonendatenUngeprueft(person1);
        person1.removePersonenVerknuepfungByPerson1(personenVerknuepfung);
      }

      if (person2 != null && !personendaten.equals(person2)) {
        markPersonendatenUngeprueft(person2);
        person2.removePersonenVerknuepfungByPerson2(personenVerknuepfung);
      }
    }

    /*
     * NOTE ELECT-1005, dass die Verknüpfungen immer gelöscht werden, um zu vermeiden, dass Verknüpfungen ohne Person1 oder Person2 existieren!
     */
    deletePersonenVerknuepfungen(personenVerknuepfungenDeleted);
  }

  protected Collection<PersonenVerknuepfung> findPersonenVerknuepfungenDeletedIfPersonendatenDeleted(Personendaten personendaten) {
    return attach(personendaten).getPersonenVerknuepfungen();
  }

  protected Collection<PersonenVerknuepfung> findPersonenVerknuepfungenDeletedIfPersonendatenEdited(Personendaten personendaten, Collection<VerknuepfungsArt> verknuepfungsArten) {
    Set<PersonenVerknuepfung> result = new HashSet<>();

    for (VerknuepfungsArt verknuepfungsArt : verknuepfungsArten) {
      result.addAll(attach(personendaten).getPersonenVerknuepfungenByVerknuepfungsArt(verknuepfungsArt));
    }

    return result;
  }

  private void deletePersonenVerknuepfungen(Collection<PersonenVerknuepfung> verknuepfungen) {
    for (PersonenVerknuepfung verknuepfung : verknuepfungen) {
      PersonenVerknuepfung personenVerknuepfung = personenVerknuepfungDAO.attach(verknuepfung);

      if (personenVerknuepfung != null) {
        personenVerknuepfungDAO.remove(personenVerknuepfung);
      }
    }
  }

  public void addKonfliktArtAndPreparePersonendatenForMehrstaatigkeitPruefSchritt(Collection<PersonenKonfliktArt> konfliktArten, Personendaten personendaten) {
    konfliktArten.add(PersonenKonfliktArt.MEHRSTAATIGKEIT_PRUEFUNG_FEHLGESCHLAGEN);

    markPersonendatenUngeprueft(personendaten);
  }

  public void addKonfliktArtAndPreparePersonendatenForWahlrechtPruefSchritt(Collection<PersonenKonfliktArt> konfliktArten, Personendaten personendaten) {
    konfliktArten.add(PersonenKonfliktArt.WAHLRECHTLICHE_PRUEFUNG_FEHLGESCHLAGEN);

    markPersonendatenUngeprueft(personendaten);
  }

  public void addKonfliktArtAndPreparePersonendatenForWertelistenAbgleichPruefSchritt(Collection<PersonenKonfliktArt> konfliktArten, Personendaten personendaten) {
    konfliktArten.add(PersonenKonfliktArt.WERTELISTEN_ABGLEICH_FEHLGESCHLAGEN);

    markPersonendatenUngeprueft(personendaten);
  }

  public void addKonfliktArtAndPreparePersonendatenForKontodatenaenderungBearbeiterBestaetigungErforderlichPruefSchritt(Collection<PersonenKonfliktArt> konfliktArten, Personendaten personendaten) {
    konfliktArten.add(PersonenKonfliktArt.KONTODATENAENDERUNG_BEARBEITER_BESTAETIGUNG_ERFORDERLICH);

    markPersonendatenUngeprueft(personendaten);
  }

  public void addKonfliktArtAndPreparePersonendatenForKontoverbindungMehrfachAngegebenPruefSchritt(Collection<PersonenKonfliktArt> konfliktArten, Personendaten personendaten) {
    konfliktArten.add(PersonenKonfliktArt.KONTOVERBINDUNG_MEHRFACH_ANGEGEBEN);

    markPersonendatenUngeprueft(personendaten);
  }

  protected void deleteKonflikte(Personendaten personendaten, Set<PersonenKonfliktArt> konfliktArten) {
    personenKonfliktEdit.delete(personendaten, konfliktArten);
  }

  public void markPersonendatenUngeprueft(Personendaten personendaten) {
    if (isZuPruefendePerson(personendaten)) {
      personendaten.setPersonStatus(PersonStatus.UNGEPRUEFT);
    }
  }

  protected boolean isZuPruefendePerson(Personendaten personendaten) {
    return !personendaten.isPersonStatusNichtZuPruefen();
  }

  private Personendaten attach(Personendaten personendaten) {
    return personendatenDAO.attach(personendaten);
  }
}
