package de.ivu.elect.presentation.stimmeingabe;

import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;
import javax.inject.Inject;
import javax.inject.Named;

import de.ivu.elect.business.ergebnis.boundary.ErgebnisSessionSelect;
import de.ivu.elect.business.ergebnis.entity.Ergebnis;
import de.ivu.elect.business.gebietsbaum.boundary.GebietSelectChangeEvent;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietConstants;
import de.ivu.elect.business.gebietsbaum.entity.GebietsartBezirksart;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gebietsbaum.entity.WahldurchfuehrungsgebietConstants;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.UseCaseSecure;
import de.ivu.elect.business.stimmeingabe.boundary.ErgebniseingangStatus;
import de.ivu.elect.business.stimmeingabe.boundary.LetzterEingangLoader;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingabestatus;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsherkunft;
import de.ivu.elect.business.wahl.entity.WahlartErgebnisart;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.jee.faces.FacesContextUtil;
import de.ivu.elect.jee.faces.PreConditionCheck;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.MessageProperty;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyStringProperty;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.presentation.AbstractElectEditView;
import de.ivu.elect.presentation.LambdaEntitySelectOneMenuProperty;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;
import de.ivu.elect.presentation.gebietsbaum.tree.AbstractNoStoreGebietsbaumView;

@RequestScoped
@Named(StimmeingabeAusbleibendView.COMPONENT_NAME)
@View(viewId = StimmeingabeAusbleibendView.VIEW_ID, useCase = UseCasePermission.STIMMEINGABE_ANZEIGEN)
public class StimmeingabeAusbleibendView extends AbstractElectEditView<Wahldurchfuehrungsgebiet> {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "stimmeingabeAusbleibendView";

  public static final String VIEW_ID = "/stimmeingabe/stimmeingabe-ausbleibend.xhtml";

  @Inject
  private transient StimmeingabeErfassenViewResolver stimmeingabeErfassenViewResolver;

  @Inject
  private transient ErgebnisSessionSelect ergebnisSessionSelect;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient ErgebniseingangStatus ergebniseingangStatus;

  @Inject
  private transient LetzterEingangLoader letzterEingangLoader;

  @Inject
  private transient WahldurchfuehrungsgebietExtendedDAO wahldurchfuehrungsgebietExtendedDAO;

  @View
  @Inject
  private transient ErgebniseingangStatusView ergebniseingangStatusView;

  @Inject
  private transient AbstractNoStoreGebietsbaumView gebietsbaumView;


  private Wahldurchfuehrungsgebiet erfassenInGebiet;

  @Nullable
  @PreConditionCheck
  public String checkPreCondition() {
    if (isGebietSelected() && ergebnisSessionSelect.isSelected() && getSelectedGebiet().isErfassungsgebietLautGebietsart()) {
      if (principal.hasUseCasePermission(UseCasePermission.GEBIET_ERGEBNISEINGABESTATUS_SETZEN_AUSBLEIBEND) || isAusbleibend()) {
        return null;
      }
    }

    return stimmeingabeErfassenViewResolver.getViewId();
  }

  @Override
  @Nullable
  protected Wahldurchfuehrungsgebiet getEntity() {
    if (getSelectedGebiet() == null) {
      return null;
    }
    return getSelectedGebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity();
  }

  @Override
  protected void addProperties(FormBuilder formBuilder, Wahldurchfuehrungsgebiet entity) {
    formBuilder.addSeparator("ausbleibend.gebiet.separator");
    formBuilder.addProperty(new ReadOnlyStringProperty(getMessage(GebietConstants.MESSAGE_KEY_ENTITY_EINZAHL), getGebietName() ));
    Ergebniseingabestatus ergebniseingabestatus = entity.getErgebniseingabestatus(getSelectedErgebnis());
    formBuilder.addProperty(new ReadOnlyStringProperty(getMessage("ausbleibend.gebiet.eingabestatus"), getMessage(ergebniseingabestatus)));

    List<Wahldurchfuehrungsgebiet> gebieteFuerDieEfasstWird = getGebieteFuerDieEfasstWird();
    if (!gebieteFuerDieEfasstWird.isEmpty()) {
      formBuilder.addProperty(new ReadOnlyStringProperty(getMessage("ausbleibend.gebiet.erfasst.fuer"),
          gebieteFuerDieEfasstWird.stream().sorted(GebietByBreadthFirstComparator.INSTANCE).map(gebietNummerFormatter::getDarstellung).collect(Collectors.joining(", "))));
    }
    if (ergebniseingabestatus.isAusbleibend()){
      if (entity.getErfasstIn()==null) {
        formBuilder.addProperty(new MessageProperty(getMessage("ausbleibend.gebiet.wird.nicht.erfasst.status.message")));
      } else {
        formBuilder.addSeparator("ausbleibend.gebiet.erfasst.separator");
        formBuilder.addProperty(new MessageProperty(getMessage("ausbleibend.gebiet.wird.erfasst.in.status.message",
            gebietNummerFormatter.getDarstellung(entity.getErfasstIn()))));
      }
    } else {

      if (isBtnAusbleibendAnderesGebietRendered()) {
        formBuilder.addSeparator("ausbleibend.gebiet.erfasst.separator");
        formBuilder.addProperty(new MessageProperty(getMessage("stimmeingabe.erfassen.action.ausbleibend.confirm.message")));
        formBuilder.addProperty(new MessageProperty(getMessage("stimmeingabe.erfassen.action.ausbleibend.confirm.message.2")));
        formBuilder.addProperty(new MessageProperty(""));

        LambdaEntitySelectOneMenuProperty<Wahldurchfuehrungsgebiet> property = new LambdaEntitySelectOneMenuProperty<>(getErfassenInGebiete(),
            this::setErfassenInGebiet, this::getErfassenInGebiet, gebietNummerFormatter::getDarstellung,
            WahldurchfuehrungsgebietConstants.MESSAGE_KEY_ERFASST_IN_ID_EINZAHL, false, true);
        property.setNoSelectionLabel(getMessage("gebiet.nicht.erfassen"));
        formBuilder.addProperty(property);
      }
    }
  }

  protected List<Wahldurchfuehrungsgebiet> getGebieteFuerDieEfasstWird() {
    return wahldurchfuehrungsgebietExtendedDAO.findByErfasstFuerGebiete(getGebiet());
  }

  public boolean isBtnAusbleibendAnderesGebietRendered() {
    return isBtnAusbleibendRendered() && !isEingangVorhanden(getSelectedGebiet());
  }

  public boolean isBtnAusbleibendRendered() {
    return hasPermission(
        UseCasePermission.GEBIET_ERGEBNISEINGABESTATUS_SETZEN_AUSBLEIBEND) && isGebietBearbeitenErlaubt() && !isErfassungGesperrt() && !isEingangVorhandenAktuelleErgebniseingangsart() && getGebieteFuerDieEfasstWird()
        .isEmpty() && ergebniseingangStatusView.isOberGebietEingabeMoeglich();
  }

  private boolean isEingangVorhandenAktuelleErgebniseingangsart() {
    return isEingangVorhandenAktuelleErgebniseingangsart(getGebiet());
  }

  private boolean isEingangVorhanden(Gebiet gebiet) {
    return letzterEingangLoader.hasAnyErgebniseingang(gebiet.getWahl().getPk(), gebiet.getPk());
  }

  private boolean isEingangVorhandenAktuelleErgebniseingangsart(Gebiet gebiet) {
    return letzterEingangLoader.bestimmeLetztenEingang(gebiet.getWahl().getPk(), gebiet.getPk(),
        Ergebniseingangsart.getKorrespondierendeErgebniseingangsart(getSelectedErgebnis())).isEingangVorhanden();
  }

  private boolean isErfassungGesperrt() {
    return !isGebietLaesstEingabeZu();
  }

  protected boolean isGebietLaesstEingabeZu() {
    return isGebietLaesstEingabeZu(getEingangsherkunftIgnorieren());
  }


  protected BooleanSupplier getEingangsherkunftIgnorieren() {
    return () -> getPrincipal().hasUseCasePermission(UseCasePermission.STIMMEINGABE_CREATE_EINGANGSHERKUNFT_IGNORIEREN);
  }

  private boolean isGebietLaesstEingabeZu(BooleanSupplier eingangsherkunftIgnorieren) {
    return isGebietSelected() && getGebiet().isEingabeMoeglich(getErgebniseingangsart(), getErgebniseingangsherkunft(), eingangsherkunftIgnorieren);
  }

  private Ergebniseingangsherkunft getErgebniseingangsherkunft() {
    return AbstractStimmeingabeErfassenView.ERGEBNISEINGANGSHERKUNFT_MANUELLE_ERFASSUNG;
  }

  private WahlartErgebnisart getErgebniseingangsart() {
    return getSelectedErgebnis().getWahl().castTo(Wahldurchfuehrung.class).getEntity().getErgebniseingangsart();

  }

  public Wahldurchfuehrungsgebiet getGebiet() {
    return getSelectedGebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity();
  }


  @Redirect
  @UseCaseSecure(UseCasePermission.GEBIET_ERGEBNISEINGABESTATUS_SETZEN_AUSBLEIBEND)
  public String ausbleibend() {
    if (isBtnAusbleibendRendered() && !isBtnAusbleibendDisabled()) {
      if (getErfassenInGebiet() != null) {
        addErrorMessage("stimmeingabe.erfassen.action.ausbleibend.gebiet.darf.nicht.gesetzt.sein.message");
        return VIEW_ID;
      }
      Ergebniseingabestatus result = ergebniseingangStatus
          .setzeGebietErgebniseingabestatus(getGebiet(), Ergebniseingabestatus.AUSBLEIBEND, getSelectedErgebnis());

      if (result.isAusbleibend()) {
        addInfoMessage("stimmeingabe.erfassen.action.ausbleibend.success.message", getGebietName());
        return VIEW_ID;
      }
    }

    addWarningMessage("stimmeingabe.erfassen.action.ausbleibend.error.message", getGebietName());
    return VIEW_ID;
  }

  private Ergebnis getSelectedErgebnis() {
    return ergebnisSessionSelect.getEntity();
  }

  private boolean isBtnAusbleibendDisabled() {
    return false;
  }

  @Redirect
  @UseCaseSecure(UseCasePermission.GEBIET_ERGEBNISEINGABESTATUS_SETZEN_AUSBLEIBEND)
  public String ausbleibendAnderesGebiet() {
    if (isBtnAusbleibendRendered() && !isBtnAusbleibendDisabled()) {
      if (getErfassenInGebiet() == null) {
        addErrorMessage("stimmeingabe.erfassen.action.ausbleibend.gebiet.muss.gesetzt.sein.message");
        return VIEW_ID;
      }

      Ergebniseingabestatus result = ergebniseingangStatus.setzeGebietErgebniseingabestatus(getGebiet(), Ergebniseingabestatus.AUSBLEIBEND,
          getSelectedErgebnis(), getErfassenInGebiet());

      if (result.isAusbleibend()) {
        addInfoMessage("stimmeingabe.erfassen.action.ausbleibend.anderes.gebiet.success.message", getGebietName(),
            gebietNummerFormatter.getDarstellung(getErfassenInGebiet()));
        FacesContextUtil.redirectToView(VIEW_ID);
        return VIEW_ID;
      }
    }

    addWarningMessage("stimmeingabe.erfassen.action.ausbleibend.error.message", getGebietName());

    return VIEW_ID;
  }

  public List<Wahldurchfuehrungsgebiet> getErfassenInGebiete() {
    return filterIsErfassenInGebiet(wahldurchfuehrungsgebietExtendedDAO.findByWahl(getSelectedErgebnis().getWahl()));
  }

  protected List<Wahldurchfuehrungsgebiet> filterIsErfassenInGebiet(List<Wahldurchfuehrungsgebiet> alleGebiete) {
    Wahldurchfuehrungsgebiet aktuellesGebiet = getGebiet();
    GebietsartBezirksart bezirksart = aktuellesGebiet.getGebietsart().getBezirksart();

    return alleGebiete.stream()
        .filter(g -> !aktuellesGebiet.equals(g))
        .filter(g -> bezirksart.equals(g.getGebietsart().getBezirksart()))
        .filter(this::isAlsErfasstInGebietMoeglich)
        .filter(getBearbeitenGebietSecurity()::isRechtVorhanden)// ELECTBB-538
        .sorted(GebietByBreadthFirstComparator.INSTANCE)
        .collect(Collectors.toList());
  }

  private boolean isAlsErfasstInGebietMoeglich(Wahldurchfuehrungsgebiet g) {
    return g.isErfassungsgebiet() && g.getErfasstIn() == null && g.isEingabeMoeglich(getErgebniseingangsart(), getErgebniseingangsherkunft(),
        getEingangsherkunftIgnorieren()) && !isEingangVorhanden(g) /*ELECT-12594*/
        ;
  }

  public Wahldurchfuehrungsgebiet getErfassenInGebiet() {
    return erfassenInGebiet;
  }

  public void setErfassenInGebiet(Wahldurchfuehrungsgebiet erfassenInGebiet) {
    this.erfassenInGebiet = erfassenInGebiet;
  }

  @Override
  public String delete() {
    throw new UnsupportedOperationException();
  }

  @Override
  protected boolean isGebietBearbeitenRechtPruefen() {
    return true;
  }

  @Override
  protected boolean isSavePermissionVorhanden() {
    return hasPermission(UseCasePermission.GEBIET_ERGEBNISEINGABESTATUS_SETZEN_AUSBLEIBEND);
  }

  @Override
  public String save() {
    throw new UnsupportedOperationException();
  }

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

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

  @Override
  public boolean isBtnCancelRendered() {
    return !isAusbleibend();
  }

  @Override
  @Redirect
  public String cancel() {
    goBack(stimmeingabeErfassenViewResolver.getViewId());
    return stimmeingabeErfassenViewResolver.getViewId();
  }

  public String getGebietName() {
    return gebietNummerFormatter.getDarstellung(getSelectedGebiet());
  }

  private boolean isAusbleibend() {
    return getGebiet() != null && getSelectedGebiet() != null && getGebiet().getErgebniseingabestatus(getSelectedErgebnis()).isAusbleibend();
  }

  public void onGebietSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) GebietSelectChangeEvent event) {
    reset();
    if (VIEW_ID.equals(FacesContextUtil.getCurrentViewId())) {
      gebietsbaumView.reset();
      FacesContextUtil.redirectToView(stimmeingabeErfassenViewResolver.getViewId());
    }
  }

}
