
package de.ivu.elect.presentation.personenpruefung;

import static org.apache.commons.lang3.StringUtils.defaultString;

import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;

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

import org.apache.deltaspike.core.api.scope.ConversationGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.gebietsbaum.entity.EntityMitGebiet;
import de.ivu.elect.business.person.entity.PersonenVerknuepfung;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.business.person.entity.VerknuepfungsStatus;
import de.ivu.elect.business.personenpruefung.boundary.PersonenKonfliktConversationSelect;
import de.ivu.elect.business.personenpruefung.boundary.dubletten.AbstractDublettenEdit;
import de.ivu.elect.business.personenpruefung.boundary.dubletten.AbstractPersonenKonfliktDublettenQueueConversationSelect;
import de.ivu.elect.business.personenpruefung.boundary.dubletten.DublettenPersonendatenPair;
import de.ivu.elect.business.personenpruefung.boundary.dubletten.DublettenPersonendatenPkPair;
import de.ivu.elect.business.personenpruefung.boundary.dubletten.DublettenPersonendatenPkPairConversationStore;
import de.ivu.elect.business.personenpruefung.boundary.dubletten.DublettenSearchParameters;
import de.ivu.elect.business.personenpruefung.entity.PersonenKonflikt;
import de.ivu.elect.business.personenpruefung.entity.PersonenKonfliktArt;
import de.ivu.elect.jee.entity.DAO;
import de.ivu.elect.jee.faces.PreConditionCheck;
import de.ivu.elect.jee.presentation.DialogActionListener;
import de.ivu.elect.jee.presentation.dynamicform.AdvancedDynaFormModel;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.jee.presentation.view.AbstractView;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;
import de.ivu.elect.presentation.person.action.AbstractPersonendatenDeleteActionResolver;
import de.ivu.elect.presentation.person.action.AbstractPersonendatenEditActionResolver;
import de.ivu.elect.presentation.personenpruefung.form.PersonenKonfliktDublettenAufloesenViewFormBuilder;
import de.ivu.elect.presentation.security.GebietSecurity;
import de.ivu.elect.presentation.security.SperrmerkmalSecurity;
import de.ivu.elect.presentation.table.AbstractColumnEntry;

// BEI ANPASSUNGEN GGF. AUCH AbstractPersonenKonfliktAufloesenView ANPASSEN!!!
//
// NOTE, dass dieser View perspektivisch den eigentlich ausgewählten Konflikt in PersonenKonfliktConversationSelect anstatt
// DublettenPersonendatenPkPairConversationStore bzw. AbstractPersonenKonfliktDublettenQueueConversationSelect auswerten sollte
// und dann auch von AbstractPersonenKonfliktAufloesenView abgeleitet werden kann!
@OnlineHilfeId("247")
public abstract class AbstractPersonenKonfliktDublettenAufloesenView<E extends Personendaten> extends AbstractView implements PersonenKonfliktAufloesenView {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "personenKonfliktDubletteAufloesenView";

  public static final String VIEW_ID = "/personenpruefung/personen-konflikt-dublette-aufloesen.xhtml";

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

  @Inject
  private transient GebietSecurity gebietSecurity;

  @Inject
  private transient SperrmerkmalSecurity sperrmerkmalSecurity;

  @Inject
  private transient PersonenKonfliktAufloesenViewViewAccessScopedStateStore viewAccessScopedState; // ELECT-8223

  @Inject
  private transient PersonenKonfliktConversationSelect konfliktSelect;

  @Inject
  @ConversationGroup(DublettenSearchParameters.class)
  private transient DublettenSearchParameters searchParameters;

  @Inject
  @ConversationGroup(DublettenPersonendatenPkPairConversationStore.class)
  private transient DublettenPersonendatenPkPairConversationStore personendatenPkPairConversationStore;

  @Inject
  private transient PersonenKonfliktDublettenAufloesenViewFormBuilder personenKonfliktDublettenAufloesenViewFormBuilder;

  private AdvancedDynaFormModel personendatenLeftModel;

  private AdvancedDynaFormModel personendatenRightModel;

  private String sperrmerkmalStyleClass;

  public AbstractPersonenKonfliktDublettenAufloesenView() {
    super();
  }

  @PostConstruct
  public void init() {
    initSearchParameters();
  }

  @PreConditionCheck
  public String checkPreCondition() {
    viewAccessScopedState.setEntity(getKonflikt());

    if (isKonfliktSelected() && isExpectedKonfliktArt(getExpectedKonfliktArt())) {
      initNextEntitiesByPersonenKonflikt(getKonflikt());
    }

    // NOTE, dass die folgenden Zeilen Legacy-Code sind, um zunächst weiterhin die bestehende Dublettensuche anhand des
    // DublettenPersonendatenPkPairConversationStore bzw. AbstractPersonenKonfliktDublettenQueueConversationSelect, bspw. für Destatis, zu unterstützen.
    if (isNextEntitySet() && (!isPersonendatenLeftOrRightPkSelected() || isPersonendatenLeftOrRightNichtGeeignet())) {
      setPersonendatenPair(findNextPersonendatenPairOrNull());
    }

    if (isPersonendatenLeftOrRightPkSelected()) {
      return null;
    }

    return cancel();
  }

  private void initNextEntitiesByPersonenKonflikt(PersonenKonflikt personenKonfliktSelected) {
    List<PersonendatenPK> result = new ArrayList<>();

    List<PersonenKonflikt> personenKonfliktSearchResult = getCurrentPersonenKonfliktSearchResult(personenKonfliktSelected.getPersonendaten());

    if (personenKonfliktSearchResult.contains(personenKonfliktSelected)) {
      int indexOfPersonenKonflikt = personenKonfliktSearchResult.indexOf(personenKonfliktSelected);

      for (int i = indexOfPersonenKonflikt; i < personenKonfliktSearchResult.size(); i++) {
        PersonenKonflikt konflikt = personenKonfliktSearchResult.get(i);

        if (getExpectedKonfliktArt().equals(konflikt.getPersonenKonfliktArt())) {
          result.add(konflikt.getPersonendaten().getPk());
        }
      }
    } else {
      // NOTE ELECTMUC-878, dass der ausgewählte Konflikt nicht im aktuellen Konflikt-Suchergebnis (!) enthalten ist.
      // Das ist bspw. der Fall, wenn die Bearbeitung des Konfliktes aus einer anderen Suche heraus, bspw. aus der
      // Wahlhelfer-Suche (!) über die Action 'Konflikt bearbeiten' am Wahlhelfer, aufgerufen wird.
      result.add(personenKonfliktSelected.getPersonendaten().getPk());
    }

    setNextEntities(result);

    // NOTE, dass der ausgewählte Konflikt hier gleich wieder vergessen wird, da er aktuell nur zur einmaligen
    // Initialisierung von DublettenPersonendatenPkPairConversationStore bzw. AbstractPersonenKonfliktDublettenQueueConversationSelect verwendet wird!
    konfliktSelect.clearEntity();
  }

  @Override
  public String getViewId() {
    return VIEW_ID;
  }

  public DublettenSearchParameters getSearchParameters() {
    return searchParameters;
  }

  protected abstract List<PersonenKonflikt> getCurrentPersonenKonfliktSearchResult(Personendaten personendaten);

  protected abstract AbstractPersonenKonfliktDublettenQueueConversationSelect getNextEntitiesSelect();

  protected boolean isKonfliktSelected() {
    return konfliktSelect.isSelected();
  }

  @Override
  public PersonenKonflikt getKonflikt() {
    return konfliktSelect.getEntity();
  }

  private boolean isExpectedKonfliktArt(PersonenKonfliktArt konfliktArt) {
    return konfliktArt.equals(getKonflikt().getPersonenKonfliktArt());
  }

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

  protected abstract void initSearchParameters();

  protected DublettenPersonendatenPkPairConversationStore getPersonendatenPkPairStore() {
    return personendatenPkPairConversationStore;
  }

  public DublettenPersonendatenPair<E> getPersonendatenPair() {
    return createPersonendatenPair(getPersonendatenPkPairStore().getPersonendatenPkPair());
  }

  // NOTE, dass auch eine Queue von nächsten Personen existiert (setEntities)
  protected void setPersonendatenPair(DublettenPersonendatenPair<E> personendatenPair) {
    getPersonendatenPkPairStore().setPersonendatenPair(personendatenPair);
  }

  private boolean isPersonendatenLeftOrRightPkSelected() {
    return getPersonendatenPkPairStore().hasLeftOrRight();
  }

  private boolean isPersonendatenLeftAndRightSelected() {
    return isPersonendatenLeftSelected() && isPersonendatenRightSelected();
  }

  // NOTE, dass die Prüfung vorbei ist, sobald einer der beiden Entitäten als "nicht geeignet" markiert wurde
  private boolean isPersonendatenLeftOrRightNichtGeeignet() {
    return isPersonendatenLeftAndRightSelected() && (getPersonendatenLeft().isPersonStatusNichtGeeignet() || getPersonendatenRight().isPersonStatusNichtGeeignet());
  }

  private boolean isNextEntitySet() {
    return getNextEntitiesSelect().isSelected();
  }

  public void setNextEntities(List<PersonendatenPK> personendatenPks) {
    getPersonendatenPkPairStore().close();

    getNextEntitiesSelect().clear();
    getNextEntitiesSelect().setPersonendatenPksZuBearbeiten(personendatenPks);
  }

  protected boolean isPersonendatenLeftSelected() {
    return getPersonendatenLeft() != null;
  }

  protected boolean isPersonendatenLeftReadonly() {
    return isReadonly(getPersonendatenLeft());
  }

  public E getPersonendatenLeft() {
    if (getPersonendatenPkPairStore().hasLeft()) {
      return getPersonendatenPair().getLeft();
    }

    return null;
  }

  public String getPersonendatenLeftHeader() {
    return getPersonendatenHeader(getPersonendatenLeft());
  }

  public String getPersonendatenLeftStyleClass() {
    return getPersonendatenStyleClass(getPersonendatenLeft());
  }

  public boolean isPersonendatenLeftModelRendered() {
    return isPersonendatenLeftSelected();
  }

  public AdvancedDynaFormModel getPersonendatenLeftModel() {
    if (personendatenLeftModel == null && isPersonendatenLeftSelected()) {
      personendatenLeftModel = createPersonendatenModel(getPersonendatenLeft(), getPersonendatenRight());
    }

    return personendatenLeftModel;
  }

  public boolean isBtnEditPersonendatenLeftRendered() {
    return !isPersonendatenLeftReadonly() && isEditPersonendatenSupported(getPersonendatenLeft());
  }

  @Redirect
  public String editPersonendatenLeft() {
    return editPersonendaten(getPersonendatenLeft());
  }

  public boolean isBtnDeletePersonendatenLeftRendered() {
    return !isPersonendatenLeftReadonly() && isDeletePersonendatenSupported(getPersonendatenLeft());
  }

  public String getDlgDeletePersonendatenLeftName() {
    return "PersonendatenLeftDeleteConfirm";
  }

  public String getDlgDeletePersonendatenLeftMessage() {
    return getDlgDeletePersonendatenMessage(getPersonendatenLeft());
  }

  @Redirect
  public String confirmDeletePersonendatenLeft() {
    return confirmDeletePersonendaten(getDlgDeletePersonendatenLeftName(), getPersonendatenLeft());
  }

  @Redirect
  public String deletePersonendatenLeft() {
    return deletePersonendaten(getPersonendatenLeft());
  }

  protected boolean isPersonendatenRightSelected() {
    return getPersonendatenRight() != null;
  }

  protected boolean isPersonendatenRightReadonly() {
    return isReadonly(getPersonendatenRight());
  }

  public E getPersonendatenRight() {
    if (getPersonendatenPkPairStore().hasRight()) {
      return getPersonendatenPair().getRight();
    }

    return null;
  }

  public String getPersonendatenRightHeader() {
    return getPersonendatenHeader(getPersonendatenRight());
  }

  public String getPersonendatenRightStyleClass() {
    return getPersonendatenStyleClass(getPersonendatenRight());
  }

  public boolean isPersonendatenRightModelRendered() {
    return isPersonendatenRightSelected();
  }

  public AdvancedDynaFormModel getPersonendatenRightModel() {
    if (personendatenRightModel == null && isPersonendatenRightSelected()) {
      personendatenRightModel = createPersonendatenModel(getPersonendatenRight(), getPersonendatenLeft());
    }

    return personendatenRightModel;
  }

  public boolean isBtnEditPersonendatenRightRendered() {
    return !isPersonendatenRightReadonly() && isEditPersonendatenSupported(getPersonendatenRight());
  }

  @Redirect
  public String editPersonendatenRight() {
    return editPersonendaten(getPersonendatenRight());
  }

  public boolean isBtnDeletePersonendatenRightRendered() {
    return !isPersonendatenRightReadonly() && isDeletePersonendatenSupported(getPersonendatenRight());
  }

  public String getDlgDeletePersonendatenRightName() {
    return "PersonendatenRightDeleteConfirm";
  }

  public String getDlgDeletePersonendatenRightMessage() {
    return getDlgDeletePersonendatenMessage(getPersonendatenRight());
  }

  @Redirect
  public String confirmDeletePersonendatenRight() {
    return confirmDeletePersonendaten(getDlgDeletePersonendatenRightName(), getPersonendatenRight());
  }

  @Redirect
  public String deletePersonendatenRight() {
    return deletePersonendaten(getPersonendatenRight());
  }

  private String getPersonendatenHeader(E personendaten) {
    return personenKonfliktDublettenAufloesenViewFormBuilder.getPersonendatenHeader(personendaten);
  }

  private String getPersonendatenStyleClass(E personendaten) {
    if (sperrmerkmalSecurity.isSperreVorhandenUndHervorzuheben(personendaten)) {
      return getSperrmerkmalStyleClass();
    }

    return "";
  }

  public String getPageHeader() {
    return getMessage("personen.konflikt.dublette.aufloesen.page.header");
  }

  public boolean isReadonly() {
    return isPersonendatenLeftReadonly() || isPersonendatenRightReadonly();
  }

  private String getSperrmerkmalStyleClass() {
    if (sperrmerkmalStyleClass == null) {
      sperrmerkmalStyleClass = defaultString(getMessage(AbstractColumnEntry.MSG_KEY_SPERRMERKMAL_STYLE_CLASS), "");
    }

    return sperrmerkmalStyleClass;
  }

  private AdvancedDynaFormModel createPersonendatenModel(E one, @Nullable E another) {
    FormBuilder formBuilder = new FormBuilder();

    addPersonendatenModelProperties(formBuilder, one, another);

    return formBuilder.createModel();
  }

  private void addPersonendatenModelProperties(FormBuilder formBuilder, E one, @Nullable E another) {
    personenKonfliktDublettenAufloesenViewFormBuilder.addProperties(formBuilder, one, another);
  }

  private boolean isEditPersonendatenSupported(E personendaten) {
    return getPersonendatenEditActionResolver().isActionPossible(personendaten);
  }

  protected String editPersonendaten(E personendaten) {
    // NOTE, dass das aktuell ausgewählte Paar zurückgesetzt wird, um dieses UND DEREN ggf. GEÄNDERTE VERKNÜPFUNG
    // nach Rückkehr auf den View immer neu zu laden!
    getPersonendatenPkPairStore().resetPersonendatenPair();

    return getPersonendatenEditActionResolver().doEdit(personendaten);
  }

  protected abstract AbstractPersonendatenEditActionResolver getPersonendatenEditActionResolver();

  private boolean isDeletePersonendatenSupported(E personendaten) {
    return getPersonendatenDeleteActionResolver().isActionPossible(personendaten);
  }

  protected String getDlgDeletePersonendatenMessage(E personendaten) {
    return getMessage("personen.konflikt.dublette.aufloesen.action.delete.confirm.message");
  }

  private String confirmDeletePersonendaten(String dialogName, E personendaten) {
    ajaxHelper.update(":" + DialogActionListener.createDialogFormId(dialogName));
    ajaxHelper.openPageDialog(dialogName);

    return null;
  }

  private String deletePersonendaten(E personendaten) {
    try {
      getPersonendatenDeleteActionResolver().doDelete(personendaten);
    } catch (Exception ex) {
      // ELECTMUC-1024
      warn(ex.getMessage(), ex);

      addErrorMessage("personen.konflikt.dublette.aufloesen.action.delete.error.message");
    }

    getPersonendatenPkPairStore().close();

    return getViewId();
  }

  protected abstract AbstractPersonendatenDeleteActionResolver getPersonendatenDeleteActionResolver();

  public boolean isBtnLoesenAlsVerschiedenRendered() {
    return !isReadonly() && isPersonendatenLeftAndRightSelected();
  }

  public String getBtnLoesenAlsVerschiedenLabel() {
    return getMessage("personen.konflikt.dublette.aufloesen.action.loesen.als", getMessage(VerknuepfungsStatus.VERSCHIEDEN));
  }

  @Redirect
  public String loeseAlsVerschieden() {
    getDublettenEdit().loeseAlsVerschieden(getPersonendatenPair());

    addMessageLoesenAlsSuccess(getPersonendatenPair(), VerknuepfungsStatus.VERSCHIEDEN);

    setPersonendatenPair(findNextPersonendatenPairOrNull());

    return getViewId();
  }

  public boolean isBtnLoesenAlsIdentischRendered() {
    return !isReadonly() && isPersonendatenLeftAndRightSelected();
  }

  public String getBtnLoesenAlsIdentischLabel() {
    return getMessage("personen.konflikt.dublette.aufloesen.action.loesen.als", getMessage(VerknuepfungsStatus.IDENTISCH));
  }

  @Redirect
  public String loeseAlsIdentisch() {
    getDublettenEdit().loeseAlsIdentisch(getPersonendatenPair());

    addMessageLoesenAlsSuccess(getPersonendatenPair(), VerknuepfungsStatus.IDENTISCH);

    setPersonendatenPair(findNextPersonendatenPairOrNull());

    return getViewId();
  }

  protected abstract AbstractDublettenEdit<E> getDublettenEdit();

  private void addMessageLoesenAlsSuccess(DublettenPersonendatenPair<E> personendatenPair, VerknuepfungsStatus verknuepfungsStatus) {
    addInfoMessage("personen.konflikt.dublette.aufloesen.action.loesen.als.success.message", getMessage(verknuepfungsStatus));
  }

  public boolean isBtnSkipRendered() {
    return true;
  }

  @Redirect
  public String skip() {
    return skipToNextPersonendatenPairByVerknuepfungsStatus(EnumSet.allOf(VerknuepfungsStatus.class));
  }

  protected String skipToNextPersonendatenPairByVerknuepfungsStatus(VerknuepfungsStatus verknuepfungsStatusZuBearbeiten) {
    return skipToNextPersonendatenPairByVerknuepfungsStatus(EnumSet.of(verknuepfungsStatusZuBearbeiten));
  }

  protected String skipToNextPersonendatenPairByVerknuepfungsStatus(Collection<VerknuepfungsStatus> verknuepfungsStatusZuBearbeiten) {
    // NOTE, dass die folgenden Zeilen Legacy-Code sind, um zunächst weiterhin die bestehende Dublettensuche anhand des
    // DublettenPersonendatenPkPairConversationStore bzw. AbstractPersonenKonfliktDublettenQueueConversationSelect, bspw. für Destatis, zu unterstützen. Eine
    // Umsetzung anhand des ausgewählten PersonenKonflikt in PersonenKonfliktConversationSelect befindet sich bspw. in
    // WahlhelferKonfliktZuordnungManuellAufloesenView#skip.
    getNextEntitiesSelect().selectNextPersonenVerknuepfungZuBearbeiten(verknuepfungsStatusZuBearbeiten);

    setPersonendatenPair(findNextPersonendatenPairOrNull());

    return getViewId();
  }

  public boolean isBtnCancelRendered() {
    return true;
  }

  @Redirect
  public String cancel() {
    getPersonendatenPkPairStore().close();

    return goBack(getParentViewId());
  }

  private E findPersonendatenOrNull(PersonendatenPK personendatenPK) {
    if (personendatenPK != null) {
      return getExtendedDao().findByPrimaryKey(personendatenPK);
    }

    return null;
  }

  protected abstract DAO<E, PersonendatenPK, ? > getExtendedDao();

  protected DublettenPersonendatenPair<E> findNextPersonendatenPairOrNull() {
    PersonenVerknuepfung personenVerknuepfung = getNextEntitiesSelect().getPersonenVerknuepfungZuBearbeiten();

    if (personenVerknuepfung == null) {
      return null;
    }

    return createPersonendatenPair(new DublettenPersonendatenPkPair<>(personenVerknuepfung.getPk(), personenVerknuepfung.getVerknuepfungsGrundlage(), personenVerknuepfung.getVerknuepfungsStatus(),
        personenVerknuepfung.getPk1(), personenVerknuepfung.getPk2()));
  }

  private DublettenPersonendatenPair<E> createPersonendatenPair(DublettenPersonendatenPkPair<PersonendatenPK> personendatenPkPair) {
    // NOTE, dass die PKs <null> sein können, etwa nachdem eine der beiden Personen gelöscht wurde
    return new DublettenPersonendatenPair<>(personendatenPkPair.getPersonenVerknuepfungPk(), personendatenPkPair.getVerknuepfungsGrundlage(), personendatenPkPair.getVerknuepfungsStatus(),
        findPersonendatenOrNull(personendatenPkPair.getLeft()), findPersonendatenOrNull(personendatenPkPair.getRight()));
  }

  private boolean isReadonly(E personendaten) {
    if (personendaten == null) {
      return true;
    }

    if (personendaten.isInstanceOf(EntityMitGebiet.class)) {
      return !gebietSecurity.getBearbeitenGebietSecurity().isRechtVorhanden(personendaten.castTo(EntityMitGebiet.class).getEntity());
    }

    return false;
  }

  protected void warn(String msg, Throwable ex) {
    LOG.warn(msg, ex);
  }
}
