
package de.ivu.elect.presentation.personenpruefung;

import static java.util.Arrays.asList;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonenregisterAbgleichStatus;
import de.ivu.elect.business.personenpruefung.entity.PersonenKonfliktArt;
import de.ivu.elect.business.personenregister.boundary.AbstractPersonenregisterSearchParameters;
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.entity.PersonenregisterAnschrift;
import de.ivu.elect.business.personenregister.entity.PersonenregisterEinwohnerstatus;
import de.ivu.elect.business.personenregister.entity.PersonenregisterEinwohnerstatusProperty;
import de.ivu.elect.business.personenregister.entity.PersonenregisterPersonendaten;
import de.ivu.elect.business.personenregister.entity.PersonenregisterPersonendatenMeta;
import de.ivu.elect.jee.presentation.dynamicform.AdvancedDynaFormModel;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.Property;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;
import de.ivu.elect.presentation.personenpruefung.form.PersonenKonfliktPersonenregisterAbgleichAufloesenViewFormBuilder;
import de.ivu.elect.presentation.personenpruefung.form.PersonenKonfliktPersonenregisterAbgleichAufloesenViewFormData;
import de.ivu.elect.presentation.personenregister.PersonenregisterPersonendatenSearchResultEntry;

@OnlineHilfeId(value = "109", mehrfachVergebenOk = true)
public abstract class AbstractPersonenKonfliktPersonenregisterAbgleichAufloesenView extends AbstractPersonenKonfliktAufloesenView {

  private static final long serialVersionUID = 1L;

  @Inject
  private transient PersonenregisterSwitch personenregisterSwitch;

  @Inject
  private transient PersonenregisterPersonendatenMeta personenregisterPersonendatenMeta;

  @Inject
  private transient PersonenregisterByPersonendatenSearchParameters personenregisterByPersonendatenSearchParameters;

  @Inject
  private transient PersonenregisterByOrdnungsmerkmalSearchParameters personenregisterByOrdnungsmerkmalSearchParameters;

  @Inject
  private transient PersonenKonfliktPersonenregisterAbgleichAufloesenViewFormData personenKonfliktPersonenregisterAbgleichAufloesenViewData;

  @Inject
  private transient PersonenKonfliktPersonenregisterAbgleichAufloesenViewFormBuilder personenKonfliktPersonenregisterAbgleichAufloesenViewFormBuilder;

  private Boolean personenregisterErreichbar;

  private AdvancedDynaFormModel personenregisterSearchParametersModel;

  private AdvancedDynaFormModel personenregisterPersonendatenModel;

  private AdvancedDynaFormModel tmpPersonendatenModel;

  private List<PersonenregisterPersonendatenSearchResultEntry> personenregisterPersonendatenSearchResult;

  private PersonenregisterPersonendatenSearchResultEntry personenregisterPersonendatenSearchResultSelection;

  protected boolean markPersonendatenValidBlankOrdnungsmerkmalErrorMessageDisplayed;

  protected AbstractPersonenKonfliktPersonenregisterAbgleichAufloesenView() {
    super();
  }

  @Override
  @PostConstruct
  public void init() {
    super.init();

    tryInitialSearch();
  }

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

  @Override
  public void preRenderView() {
    super.preRenderView();

    if (!isAbfrageMoeglich()) {
      addMessagePersonenregisterNichtErreichbar();
    }
  }

  private void tryInitialSearch() {
    if (isAbfrageMoeglich() && isKonfliktSelected()) {
      runInitialSearch(getPersonendaten());
    }
  }

  protected boolean isAbfrageMoeglich() {
    return isPersonenregisterErreichbar();
  }

  private void runInitialSearch(Personendaten personendaten) {
    if (personendaten != null) {
      if (isSearchPersonenregisterByOrdnungsmerkmalSupported() && personendaten.hasOrdnungsmerkmal()) {
        initPersonenregisterByOrdnungsmerkmalSearchParameters(personendaten);

        searchPersonenregisterByOrdnungsmerkmal();
      } else if (isSearchPersonenregisterByPersonendatenSupported()) {
        initPersonenregisterByPersonendatenSearchParameters(personendaten);

        searchPersonenregisterByPersonendaten();
      }
    }
  }

  public boolean isPersonenregisterErreichbar() {
    if (personenregisterErreichbar == null) {
      // ELECTVAPP-1142
      if (getMandant() == null) {
        return false;
      }

      personenregisterErreichbar = getPersonenregister().isErreichbar(getMandant());
    }

    return personenregisterErreichbar;
  }

  protected boolean isSearchPersonenregisterByEinwohnerstatusSupported() {
    return getPersonenregister().isSearchByEinwohnerstatusSupported();
  }

  private boolean isSearchPersonenregisterByPersonendatenSupported() {
    return getPersonenregister().isSearchByPersonendatenManuellSupported(getMandant());
  }

  private boolean isSearchPersonenregisterByOrdnungsmerkmalSupported() {
    return getPersonenregister().isSearchByOrdnungsmerkmalManuellSupported(getMandant());
  }

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

  private void initPersonenregisterByPersonendatenSearchParameters(Personendaten personendaten) {
    personenregisterByPersonendatenSearchParameters.setEinwohnerstatus(PersonenregisterEinwohnerstatus.AKTIV); // ELECTDD-743

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

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

  private void initPersonenregisterByOrdnungsmerkmalSearchParameters(Personendaten personendaten) {
    personenregisterByOrdnungsmerkmalSearchParameters.setEinwohnerstatus(PersonenregisterEinwohnerstatus.AKTIV); // ELECTDD-743

    personenregisterByOrdnungsmerkmalSearchParameters.setOrdnungsmerkmal(personendaten.getOrdnungsmerkmal());
  }

  private void initContextPersonenregisterSearchParameters(AbstractPersonenregisterSearchParameters personenregisterSearchParameters, Personendaten personendaten) {
    personenregisterSearchParameters.setWahltermin(personendaten.getWahltermin());
  }

  public AdvancedDynaFormModel getPersonenregisterSearchParametersModel() {
    if (personenregisterSearchParametersModel == null) {
      personenregisterSearchParametersModel = createPersonenregisterSearchParametersModel();
    }

    return personenregisterSearchParametersModel;
  }

  private AdvancedDynaFormModel createPersonenregisterSearchParametersModel() {
    FormBuilder formBuilder = new FormBuilder();

    resolveSearchPersonenregisterByEinwohnerstatusProperty().ifPresent(formBuilder::addProperty);

    if (isSearchPersonenregisterByPersonendatenSupported()) {
      personenKonfliktPersonenregisterAbgleichAufloesenViewFormBuilder.addProperties(formBuilder, personenregisterByPersonendatenSearchParameters);
    }

    if (isSearchPersonenregisterByOrdnungsmerkmalSupported()) {
      personenKonfliktPersonenregisterAbgleichAufloesenViewFormBuilder.addProperties(formBuilder, personenregisterByOrdnungsmerkmalSearchParameters);
    }

    return formBuilder.createModel();
  }

  public List<PersonenregisterPersonendatenSearchResultEntry> getPersonenregisterPersonendatenSearchResult() {
    return personenregisterPersonendatenSearchResult;
  }

  private void resetPersonenregisterPersonendatenSearchResult() {
    personenregisterPersonendatenSearchResult = new ArrayList<>();
  }

  public PersonenregisterPersonendatenSearchResultEntry getPersonenregisterPersonendatenSearchResultSelection() {
    return personenregisterPersonendatenSearchResultSelection;
  }

  public void setPersonenregisterPersonendatenSearchResultSelection(PersonenregisterPersonendatenSearchResultEntry personenregisterPersonendatenSearchResultSelection) {
    this.personenregisterPersonendatenSearchResultSelection = personenregisterPersonendatenSearchResultSelection;

    resetTmpPersonendatenModel();

    resetPersonenregisterPersonendatenModel();

    if (personenregisterPersonendatenSearchResultSelection != null) {
      setPersonenregisterPersonendaten(personenregisterPersonendatenSearchResultSelection.toPersonenregisterPersonendaten());
    }
  }

  private void resetPersonenregisterPersonendatenSearchResultSelection() {
    personenregisterPersonendatenSearchResultSelection = null;
  }

  // Dies sind die Daten, die LINKS stehen
  protected Personendaten getTmpPersonendaten() {
    if (personenKonfliktPersonenregisterAbgleichAufloesenViewData.getTmpPersonendaten() == null) {
      setTmpPersonendaten(getPersonendaten());
    }

    return personenKonfliktPersonenregisterAbgleichAufloesenViewData.getTmpPersonendaten();
  }

  protected void setTmpPersonendaten(Personendaten personendaten) {
    personenKonfliktPersonenregisterAbgleichAufloesenViewData.setTmpPersonendaten(personendaten);
  }

  private void resetTmpPersonendaten() {
    setTmpPersonendaten(null);
  }

  private boolean isPersonenregisterPersonendatenSelected() {
    return getPersonenregisterPersonendaten() != null;
  }

  // Dies sind die Daten, die RECHTS stehen
  protected PersonenregisterPersonendaten getPersonenregisterPersonendaten() {
    return personenKonfliktPersonenregisterAbgleichAufloesenViewData.getPersonenregisterPersonendaten();
  }

  protected void setPersonenregisterPersonendaten(PersonenregisterPersonendaten personenregisterPersonendaten) {
    personenKonfliktPersonenregisterAbgleichAufloesenViewData.setPersonenregisterPersonendaten(personenregisterPersonendaten);
  }

  private void resetPersonenregisterPersonendaten() {
    setPersonenregisterPersonendaten(null);
  }

  public String getPersonendatenHeader() {
    return konfliktMeta.getPersonendatenEinzahlMessage();
  }

  public boolean isPersonendatenModelRendered() {
    return true;
  }

  public AdvancedDynaFormModel getPersonendatenModel() {
    if (tmpPersonendatenModel == null) {
      tmpPersonendatenModel = createTmpPersonendatenModel();
    }

    return tmpPersonendatenModel;
  }

  private AdvancedDynaFormModel createTmpPersonendatenModel() {
    FormBuilder formBuilder = new FormBuilder();

    addTmpPersonendatenModelProperties(formBuilder);

    if (isReadonly()) {
      formBuilder.globalReadonly();
    }

    return formBuilder.createModel();
  }

  private void addTmpPersonendatenModelProperties(FormBuilder formBuilder) {
    personenKonfliktPersonenregisterAbgleichAufloesenViewFormBuilder.addVerifiedProperties(formBuilder, getTmpPersonendaten(), getPersonenregisterPersonendaten());
  }

  private void resetTmpPersonendatenModel() {
    tmpPersonendatenModel = null;
  }

  public String getPersonenregisterPersonendatenHeader() {
    return personenregisterPersonendatenMeta.getEinzahlMessage();
  }

  public boolean isPersonenregisterPersonendatenModelRendered() {
    return isPersonenregisterPersonendatenSelected();
  }

  public AdvancedDynaFormModel getPersonenregisterPersonendatenModel() {
    if (personenregisterPersonendatenModel == null && isPersonenregisterPersonendatenSelected()) {
      personenregisterPersonendatenModel = createPersonenregisterPersonendatenModel();
    }

    return personenregisterPersonendatenModel;
  }

  private AdvancedDynaFormModel createPersonenregisterPersonendatenModel() {
    FormBuilder formBuilder = new FormBuilder();

    addPersonenregisterPersonendatenModelProperties(formBuilder);

    return formBuilder.createModel();
  }

  private void addPersonenregisterPersonendatenModelProperties(FormBuilder formBuilder) {
    personenKonfliktPersonenregisterAbgleichAufloesenViewFormBuilder.addProperties(formBuilder, getPersonenregisterPersonendaten());
  }

  private void resetPersonenregisterPersonendatenModel() {
    personenregisterPersonendatenModel = null;
  }

  public boolean isBtnSearchPersonenregisterByPersonendatenRendered() {
    return isSearchPersonenregisterByPersonendatenSupported();
  }

  public boolean isBtnSearchPersonenregisterByPersonendatenDisabled() {
    return isReadonly() || !isPersonenregisterErreichbar();
  }

  @Redirect
  public String searchPersonenregisterByPersonendaten() {
    initContextPersonenregisterSearchParameters(personenregisterByPersonendatenSearchParameters, getPersonendaten());

    return runSearch(new PersonenregisterSearch() {

      private static final long serialVersionUID = 1L;

      @Override
      public PersonenregisterSearchResult< ? extends PersonenregisterPersonendaten> run() {
        return getPersonenregister().search(getMandant(), UUID.randomUUID(), personenregisterByPersonendatenSearchParameters);
      }
    });
  }

  public boolean isBtnSearchPersonenregisterByOrdnungsmerkmalRendered() {
    return isSearchPersonenregisterByOrdnungsmerkmalSupported();
  }

  public boolean isBtnSearchPersonenregisterByOrdnungsmerkmalDisabled() {
    return isReadonly() || !isPersonenregisterErreichbar();
  }

  @Redirect
  public String searchPersonenregisterByOrdnungsmerkmal() {
    initContextPersonenregisterSearchParameters(personenregisterByOrdnungsmerkmalSearchParameters, getPersonendaten());

    return runSearch(new PersonenregisterSearch() {

      private static final long serialVersionUID = 1L;

      @Override
      public PersonenregisterSearchResult< ? extends PersonenregisterPersonendaten> run() {
        return getPersonenregister().search(getMandant(), UUID.randomUUID(), personenregisterByOrdnungsmerkmalSearchParameters);
      }
    });
  }

  protected String runSearch(PersonenregisterSearch personenregisterSearch) {
    resetPersonenregisterPersonendatenSearchResult();
    resetPersonenregisterPersonendatenSearchResultSelection();

    resetTmpPersonendatenModel();

    resetPersonenregisterPersonendaten();
    resetPersonenregisterPersonendatenModel();

    if (!isReadonly()) {
      applySearchPersonenregisterByEinwohnerstatusProperty();

      try {
        PersonenregisterSearchResult< ? extends PersonenregisterPersonendaten> personenregisterSearchResult = personenregisterSearch.run();

        if (personenregisterSearchResult.hasException()) {
          if (personenregisterSearchResult.hasSperrberechtigungFehltException()) {
            addMessageSperrberechtigungFehltException();
          } else {
            addMessagePersonenregisterSearchException(personenregisterSearchResult.getException());
          }
        } else {
          for (PersonenregisterPersonendaten personenregisterPersonendaten : personenregisterSearchResult.getPersonenregisterPersonendaten()) {
            if (personenregisterPersonendaten != null) {
              personenregisterPersonendatenSearchResult.addAll(mapSearchResultEntryPerAnschriftArt(personenregisterPersonendaten));
            }
          }

          // NOTE ELECT-3036, dass bei mehreren Einträgen keine Vorauswahl getroffen werden soll/darf.
          if (personenregisterPersonendatenSearchResult.size() == 1) {
            setPersonenregisterPersonendatenSearchResultSelection(personenregisterPersonendatenSearchResult.get(0));
          }
        }
      } catch (Exception ex) {
        addMessagePersonenregisterSearchException(ex);
      }
    }

    return getViewId();
  }

  protected List<PersonenregisterPersonendatenSearchResultEntry> mapSearchResultEntryPerAnschriftArt(PersonenregisterPersonendaten personenregisterPersonendaten) {
    List<PersonenregisterPersonendatenSearchResultEntry> result = new ArrayList<>();

    // NOTE, dass dem Suchergebnis für JEDE Person PRO Anschrift ein Eintrag hinzugefügt wird!
    if (personenregisterPersonendaten.hasAnschrift()) {
      for (PersonenregisterAnschrift personenregisterAnschrift : personenregisterPersonendaten.getAnschriften()) {
        result.add(new PersonenregisterPersonendatenSearchResultEntry(personenregisterPersonendaten, asList(personenregisterAnschrift)));
      }
    } else {
      result.add(new PersonenregisterPersonendatenSearchResultEntry(personenregisterPersonendaten));
    }

    return result;
  }

  private void applySearchPersonenregisterByEinwohnerstatusProperty() {
    resolveSearchPersonenregisterByEinwohnerstatusProperty() //
        .map(PersonenregisterEinwohnerstatusProperty::getValue) //
        .filter(Objects::nonNull) //
        .ifPresent(einwohnerstatus -> {
          personenregisterByPersonendatenSearchParameters.setEinwohnerstatus(einwohnerstatus);
          personenregisterByOrdnungsmerkmalSearchParameters.setEinwohnerstatus(einwohnerstatus);
        });
  }

  // Suchparameter Einwohnerstatus soll nur einmalig angezeigt werden!
  private Optional<PersonenregisterEinwohnerstatusProperty> resolveSearchPersonenregisterByEinwohnerstatusProperty() {
    if (isSearchPersonenregisterByEinwohnerstatusSupported()) {
      if (isSearchPersonenregisterByPersonendatenSupported()) {
        return Optional.of(personenregisterByPersonendatenSearchParameters.getEinwohnerstatusProperty());
      }

      if (isSearchPersonenregisterByOrdnungsmerkmalSupported()) {
        return Optional.of(personenregisterByOrdnungsmerkmalSearchParameters.getEinwohnerstatusProperty());
      }
    }

    return Optional.empty();
  }

  @Override
  protected String markPersonendatenInvalidAndGoBack() {
    updatePersonendatenAbgleichDurchgefuehrt(getPersonendaten());

    return super.markPersonendatenInvalidAndGoBack();
  }

  @Override
  protected String getMarkPersonendatenInvalidMsgKey() {
    return "personen.konflikt.personenregister.abgleich.aufloesen.action.personendaten.NICHT_GEEIGNET.konflikt.IST_ABLEHNUNGSGRUND";
  }

  @Override
  public String markPersonendatenValidAndGoBack() {
    if (getTmpPersonendaten().hasOrdnungsmerkmal() || markPersonendatenValidBlankOrdnungsmerkmalErrorMessageDisplayed) {
      updatePersonendatenAbgleichDurchgefuehrt(getPersonendaten());

      return super.markPersonendatenValidAndGoBack();
    }

    addWarningMessage(getMarkPersonendatenValidMsgKey() + ".ordnungsmerkmal.error.message", getPersonenregister().getKurzname());

    markPersonendatenValidBlankOrdnungsmerkmalErrorMessageDisplayed = true;

    return getViewId();
  }

  @Override
  protected String getMarkPersonendatenValidMsgKey() {
    return "personen.konflikt.personenregister.abgleich.aufloesen.action.personendaten.GEEIGNET.konflikt.IRRELEVANT";
  }

  public boolean isBtnMarkPersonendatenValidAndAuswaertigRendered() {
    // NOTE, dass diese Aktion momentan nur im WOS relevant ist!
    return isBtnMarkPersonendatenValidRendered();
  }

  public String getBtnMarkPersonendatenValidAndAuswaertigLabel() {
    return getMessage(getMarkPersonendatenValidAndAuswaertigMsgKeyPrefix());
  }

  @Redirect
  public abstract String markPersonendatenValidAndAuswaertig();

  protected String markPersonendatenValidAndAuswaertigAndGoBack() {
    if (!getTmpPersonendaten().hasOrdnungsmerkmal()) {
      Personendaten personendaten = getPersonendaten();

      if (personendaten != null) {
        updatePersonendatenAbgleichDurchgefuehrt(personendaten); // NOTE ELECTMUC-947, dass auch beim Markieren als auswärtig, manuelle Anpassungen übernommen werden sollen!

        personendaten.setAuswaertig(true);

        updatePersonendatenGeeignetAndKonfliktIrrelevant(getKonflikt());
      }

      return clearKonfliktSelectedAndGoBack();
    }

    addErrorMessage(getMarkPersonendatenValidAndAuswaertigMsgKeyPrefix() + ".ordnungsmerkmal.error.message", getPersonenregister().getKurzname());

    return getViewId();
  }

  private String getMarkPersonendatenValidAndAuswaertigMsgKeyPrefix() {
    return "personen.konflikt.personenregister.abgleich.aufloesen.action.personendaten.GEEIGNET.auswaertig.konflikt.IRRELEVANT";
  }

  protected void updatePersonendatenAbgleichDurchgefuehrt(Personendaten personendaten) {
    applyTmpPersonendatenPrePersist(personendaten);

    if (isPersonenregisterPersonendatenSelected()) {
      applyPersonenregisterPersonendatenPrePersist(personendaten);
    }

    personendaten.setPersonenregisterAbgleichStatus(PersonenregisterAbgleichStatus.ABGLEICH_DURCHGEFUEHRT);
    personendaten.setPersonenregisterAbgleichZeitpunkt(new Date());
  }

  protected void applyTmpPersonendatenPrePersist(Personendaten to) {
    personenKonfliktPersonenregisterAbgleichAufloesenViewData.applyTmpPersonendaten(to);
  }

  protected void applyPersonenregisterPersonendatenPrePersist(Personendaten to) {
    // NOTE, dass standardmäßig nichts automatisch zu übernehmen ist.
  }

  public boolean isBtnResetPersonendatenRendered() {
    return !isReadonly();
  }

  @Redirect
  public String resetPersonendaten() {
    resetTmpPersonendaten();
    resetTmpPersonendatenModel();

    markPersonendatenValidBlankOrdnungsmerkmalErrorMessageDisplayed = false;

    return getViewId();
  }

  public boolean isBtnApplyAllRendered() {
    return isPersonenregisterPersonendatenSelected() && !isReadonly();
  }

  public void applyAll() {
    applyPersonenregisterPersonendaten();

    resetTmpPersonendatenModel();
  }

  private void applyPersonenregisterPersonendaten() {
    personenKonfliktPersonenregisterAbgleichAufloesenViewData.applyPersonenregisterPersonendaten();
  }

  public void apply(Property property) {
    applyPersonenregisterPersonendatum(property);

    resetTmpPersonendatenModel();
  }

  private void applyPersonenregisterPersonendatum(Property property) {
    personenKonfliktPersonenregisterAbgleichAufloesenViewData.applyPersonenregisterPersonendatum(property);
  }

  private void addMessagePersonenregisterNichtErreichbar() {
    addWarningMessage("personenregister.nicht.erreichbar.failure.message", getPersonenregister().getName());
  }

  private void addMessageSperrberechtigungFehltException() {
    addWarningMessage("personenregister.sperrberechtigung.fehlt.failure.message");
  }

  private void addMessagePersonenregisterSearchException(Exception ex) {
    addErrorMessage(ex);
  }

  protected interface PersonenregisterSearch extends Serializable {

    PersonenregisterSearchResult< ? extends PersonenregisterPersonendaten> run();
  }
}
