
package de.ivu.elect.presentation.stimmzettel;

import static java.util.Arrays.asList;

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

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 org.primefaces.event.FileUploadEvent;

import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.datei.entity.FileTypeResolver;
import de.ivu.elect.business.ergebnis.entity.Ergebnisart;
import de.ivu.elect.business.gebietsbaum.boundary.GebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.boundary.GebietSelectChangeEvent;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPK;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingangErfasser;
import de.ivu.elect.business.stimmeingabe.entity.ErgebniseingangExceptionContext;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsherkunft;
import de.ivu.elect.business.stimmzettel.boundary.StimmzettelExtendedDAO;
import de.ivu.elect.business.stimmzettel.boundary.StimmzettelKopfdatumExtendedDAO;
import de.ivu.elect.business.stimmzettel.boundary.StimmzettelWesImport;
import de.ivu.elect.business.stimmzettel.control.StimmzettelImportErgebniseingangErzeugen;
import de.ivu.elect.business.stimmzettel.entity.AbstractStimmzettelKopfdatum;
import de.ivu.elect.business.stimmzettel.entity.Stimmzettel;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelTeam;
import de.ivu.elect.business.stimmzettel.entity.WesStimmzettelAusDateiInformationen;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.jee.entity.customdata.EmptyCustomData;
import de.ivu.elect.jee.faces.FacesContextUtil;
import de.ivu.elect.jee.faces.upload.UploadedFileWithCanonicalFilename;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.LambdaBooleanProperty;
import de.ivu.elect.jee.presentation.dynamicform.MessageProperty;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyBooleanProperty;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyLongProperty;
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.View;
import de.ivu.elect.presentation.ViewHavingFileUpload;
import de.ivu.elect.presentation.gebietsbaum.tree.AbstractNoStoreGebietsbaumView;
import de.ivu.elect.presentation.stimmeingabe.StimmeingabeErfassenViewResolver;
import de.ivu.elect.presentation.wahl.action.WahlZurBearbeitungAuswaehlenAction;

@RequestScoped
@Named(StimmzettelWesImportView.COMPONENT_NAME)
@View(viewId = StimmzettelWesImportView.VIEW_ID, useCase = UseCasePermission.STIMMZETTEL_WES_IMPORTIEREN)
public class StimmzettelWesImportView extends AbstractElectEditView<Stimmzettel> implements ViewHavingFileUpload {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "stimmzettelWesImportView";

  public static final String VIEW_ID = "/stimmzettel/stimmzettel-wes-import.xhtml";

  private static final boolean ERGEBNISEINGANG_ERSTELLEN_DEFAULT = true;

  @Inject
  private transient FileTypeResolver fileTypeResolver;

  @Inject
  private transient StimmzettelWesImportStore stimmzettelWesImportStore;

  @Inject
  private transient StimmzettelWesImport stimmzettelWesImport;

  @Inject
  private transient GebietExtendedDAO gebietExtendedDAO;

  @Inject
  private transient WahlZurBearbeitungAuswaehlenAction wahlZurBearbeitungAuswaehlenAction;

  @Inject
  private transient StimmzettelExtendedDAO stimmzettelExtendedDAO;

  @Inject
  private transient StimmzettelKopfdatumExtendedDAO stimmzettelKopfdatumExtendedDAO;

  @Inject
  private transient WahldurchfuehrungsgebietExtendedDAO wahldurchfuehrungsgebietExtendedDAO;

  @Inject
  private transient StimmzettelStimmeingabeMoeglichStore stimmzettelStimmeingabeMoeglichStore;

  @Inject
  private transient AbstractNoStoreGebietsbaumView gebietsbaumView;

  @Inject
  private transient StimmzettelImportErgebniseingangErzeugen stimmzettelImportErgebniseingangErzeugen;

  @Inject
  private transient StimmeingabeErfassenViewResolver stimmeingabeErfassenViewResolver;

  private boolean ergebniseingangErstellenWennMoeglich = ERGEBNISEINGANG_ERSTELLEN_DEFAULT;

  public StimmzettelWesImportView() {
    super();
  }

  @Override
  protected Stimmzettel getEntity() {
    return new Stimmzettel(EmptyCustomData.INSTANCE);
  }

  @Override
  protected void addProperties(FormBuilder formBuilder, Stimmzettel entity) {
    formBuilder.addSeparator("stimmzettel.wes.import.datei.separator");

    if (stimmzettelWesImportStore.isDateiVorhanden()) {
      WesStimmzettelAusDateiInformationen wesStimmzettelAusDateiInformationen = stimmzettelWesImportStore.getWesStimmzettelAusDateiInformationen();
      Gebiet gebietInDatei = bestimmeGebiet(wesStimmzettelAusDateiInformationen);

      if (gebietInDatei == null) {
        formBuilder.addProperty(new MessageProperty(getMessage("stimmzettel.wes.import.datei.kein.gebiet.gefunden")));
        formBuilder.addProperty(new MessageProperty("\u00A0"));
      } else {
        formBuilder.addProperty(new ReadOnlyStringProperty(getMessage("stimmzettel.wes.import.wahl.in.datei"), gebietInDatei.getWahl().getName()));
        formBuilder.addProperty(new ReadOnlyStringProperty(getMessage("stimmzettel.wes.import.gebiet.in.datei"), formatGebietName(gebietInDatei)));
        formBuilder.addProperty(new ReadOnlyStringProperty(getMessage("stimmzettel.wes.import.team.in.datei"), getMessage(wesStimmzettelAusDateiInformationen.getStimmzettelTeam())));
        formBuilder.addProperty(new ReadOnlyLongProperty(getMessage("stimmzettel.wes.import.version.in.datei"), wesStimmzettelAusDateiInformationen.getVersion()));
        formBuilder.addProperty(new ReadOnlyLongProperty(getMessage("stimmzettel.wes.import.anzahl.stimmzettel.in.datei"), wesStimmzettelAusDateiInformationen.getAnzahlStimmzettel()));

        if (wesStimmzettelAusDateiInformationen.getEnthalteneStimmzettelTeamsAnzahlStimmzettel().size() > 1) {
          for (StimmzettelTeam stimmzettelTeam : StimmzettelTeam.values()) {
            if (wesStimmzettelAusDateiInformationen.getEnthalteneStimmzettelTeamsAnzahlStimmzettel().containsKey(stimmzettelTeam)) {
              formBuilder.addProperty(new ReadOnlyLongProperty(getMessage("stimmzettel.wes.import.anzahl.stimmzettel.in.datei.fuer.team", getMessage(stimmzettelTeam)),
                  wesStimmzettelAusDateiInformationen.getEnthalteneStimmzettelTeamsAnzahlStimmzettel().get(stimmzettelTeam)));
            }
          }
        }

        formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("stimmzettel.wes.import.signatur.in.datei"), wesStimmzettelAusDateiInformationen.getSignaturGueltig()));

        if (Boolean.TRUE.equals(wesStimmzettelAusDateiInformationen.getSignaturGueltig())) {
          formBuilder.withSuccessMessage(messages.getMessageForMessageKey("stimmzettel.wes.import.signatur.gueltig.success"));
        } else {
          formBuilder.withErrorMessage(messages.getMessageForMessageKey("stimmzettel.wes.import.signatur.gueltig.warnung"));
          if (isSignaturGueltigOderRechtVorhanden(false)) {
            addWarningMessage("stimmzettel.wes.import.signatur.ungueltig.hinweis.moeglich");
          } else {
            addWarningMessage("stimmzettel.wes.import.signatur.ungueltig.hinweis");
          }
        }

        if (isStimmzettelImportMoeglich()) {
          formBuilder.addSeparator("stimmzettel.wes.import.db.separator");
          formBuilder.addProperty(new ReadOnlyLongProperty(getMessage("stimmzettel.wes.import.version.in.db"), bestimmeVersionInDb(wesStimmzettelAusDateiInformationen.getStimmzettelTeam())));
          for (StimmzettelTeam stimmzettelTeam : StimmzettelTeam.values()) {
            if (wesStimmzettelAusDateiInformationen.getEnthalteneStimmzettelTeamsAnzahlStimmzettel().containsKey(stimmzettelTeam)) {
              formBuilder.addProperty(
                  new ReadOnlyLongProperty(getMessage("stimmzettel.wes.import.anzahl.stimmzettel.in.db.fuer.team", getMessage(stimmzettelTeam)), bestimmeAnzahlStimmzettelInDb(stimmzettelTeam)));
            }
          }

          formBuilder.addProperty(new LambdaBooleanProperty(getMessage("stimmzettel.wes.import.ergebnis.eingang.erstellen"), false, this::setErgebniseingangErstellenWennMoeglich,
              this::isErgebniseingangErstellenWennMoeglich));
        }
      }
    } else {
      formBuilder.addProperty(new MessageProperty(getMessage("stimmzettel.wes.import.datei.keine.ausgewaehlt")));
      formBuilder.addProperty(new MessageProperty("\u00A0"));
    }
  }

  @Nullable
  private Long bestimmeAnzahlStimmzettelInDb(StimmzettelTeam stimmzettelTeam) {
    Gebiet selectedGebiet = getSelectedGebiet();

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

    return (long) stimmzettelExtendedDAO.findByGebietAndStimmzettelteam(selectedGebiet, stimmzettelTeam).size();
  }

  @Nullable
  private Long bestimmeVersionInDb(StimmzettelTeam stimmzettelTeam) {
    Gebiet selectedGebiet = getSelectedGebiet();

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

    return stimmzettelKopfdatumExtendedDAO.findByGebiet(selectedGebiet).stream()
        .filter(stimmzettelKopfdatum -> stimmzettelTeam.getStimmzettelKopfdatumVersionsnummer().equals(stimmzettelKopfdatum.getStimmzettelKopfdatumArt())).findFirst()
        .map(AbstractStimmzettelKopfdatum::parseWertAsLong).orElse(null);
  }

  private boolean isStimmzettelImportMoeglich() {
    WesStimmzettelAusDateiInformationen wesStimmzettelAusDateiInformationen = stimmzettelWesImportStore.getWesStimmzettelAusDateiInformationen();

    Gebiet gebietInDatei = bestimmeGebiet(wesStimmzettelAusDateiInformationen);

    return gebietInDatei != null && gebietInDatei.equals(getSelectedGebiet()) && wesStimmzettelAusDateiInformationen.getStimmzettelTeam().isFestgelegt()
        && getBearbeitenGebietSecurity().isRechtVorhanden(gebietInDatei) && isStimmeingabeMoeglich(gebietInDatei)
        && isSignaturGueltigOderRechtVorhanden(Boolean.TRUE.equals(wesStimmzettelAusDateiInformationen.getSignaturGueltig()));
  }

  private boolean isSignaturGueltigOderRechtVorhanden(boolean signaturGueltig) {
    return signaturGueltig || hasPermission(UseCasePermission.STIMMZETTEL_WES_IMPORTIEREN_OHNE_GUELTIGE_SIGNATUR);
  }

  private boolean isStimmeingabeMoeglich(Gebiet gebietInDatei) {
    if (gebietInDatei == null) {
      return false;
    }

    return stimmzettelStimmeingabeMoeglichStore.isStimmeingabeMoeglich(gebietInDatei);
  }

  @Override
  public String delete() {
    return null;
  }

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

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

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

  @Override
  public String save() {
    return null;
  }

  @Override
  public boolean isBtnCancelRendered() {
    return true;
  }

  @Override
  @Redirect
  public String cancel() {
    return goBack(StimmzettelStatusuebersichtView.VIEW_ID);
  }

  @Override
  public void handleFileUpload(FileUploadEvent event) {
    reset();

    stimmzettelWesImportStore.handleFileUpload(new UploadedFileWithCanonicalFilename(event.getFile()));

    if (isGebietSelected()) {
      Gebiet gebietInDatei = bestimmeGebiet(stimmzettelWesImportStore.getWesStimmzettelAusDateiInformationen());

      if (gebietInDatei != null && gebietInDatei.equals(getSelectedGebiet())) {
        pruefeGebiet(gebietInDatei);
      }
    }
  }

  @Override
  public String getFileUploadLabel() {
    return getMessage("stimmzettel.wes.importieren.label", fileTypeResolver.formatFileTypes(getFileUploadFileTypes()));
  }

  @Override
  public String getFileUploadAllowTypes() {
    return FILE_UPLOAD_ALLOW_TYPES_ALL;
  }

  @Override
  public String getFileUploadAccept() {
    return fileTypeResolver.getFileTypesHtmlAccept(getFileUploadFileTypes());
  }

  @Override
  public List<FileType> getFileUploadFileTypes() {
    return asList(FileType.ALL);
  }

  @Override
  protected void resetModel() {
    super.resetModel();

    stimmzettelWesImportStore.reset();

    ergebniseingangErstellenWennMoeglich = ERGEBNISEINGANG_ERSTELLEN_DEFAULT;
  }

  public String getFilename() {
    return stimmzettelWesImportStore.getDateiname();
  }

  @Nullable
  private Gebiet bestimmeGebiet(WesStimmzettelAusDateiInformationen wesStimmzettelAusDateiInformationen) {
    GebietPK gebietPK = stimmzettelWesImport.findeGebiet(getMandant(), wesStimmzettelAusDateiInformationen);

    if (gebietPK != null) {
      return gebietExtendedDAO.findByPrimaryKey(gebietPK);
    }

    return null;
  }

  public boolean isWahlGebietWechselnRendered() {
    WesStimmzettelAusDateiInformationen wesStimmzettelAusDateiInformationen = stimmzettelWesImportStore.getWesStimmzettelAusDateiInformationen();

    Gebiet gebietInDatei = bestimmeGebiet(wesStimmzettelAusDateiInformationen);

    return gebietInDatei != null && !gebietInDatei.equals(getSelectedGebiet());
  }

  public String getWahlGebietWechselnLabel() {
    WesStimmzettelAusDateiInformationen wesStimmzettelAusDateiInformationen = stimmzettelWesImportStore.getWesStimmzettelAusDateiInformationen();

    Gebiet gebietInDatei = bestimmeGebiet(wesStimmzettelAusDateiInformationen);

    if (gebietInDatei != null) {
      Gebiet aktuellesGebiet = getSelectedGebiet();

      if (!gebietInDatei.equals(aktuellesGebiet)) {
        Wahl aktuelleWahl = getSelectedWahl();

        if (gebietInDatei.getWahl().equals(aktuelleWahl)) {
          return getMessage("stimmzettel.wes.import.gebiet.wechseln", formatGebietName(gebietInDatei));
        }

        return getMessage("stimmzettel.wes.import.wahl.gebiet.wechseln", formatGebietName(gebietInDatei), gebietInDatei.getWahl().getName());
      }
    }

    return ""; // Button wird nicht angezeigt
  }

  @Redirect
  public String wahlGebietWechseln() {
    WesStimmzettelAusDateiInformationen wesStimmzettelAusDateiInformationen = stimmzettelWesImportStore.getWesStimmzettelAusDateiInformationen();

    Gebiet gebietInDatei = bestimmeGebiet(wesStimmzettelAusDateiInformationen);

    if (gebietInDatei != null) {
      if (wahlZurBearbeitungAuswaehlenAction.isActionPossible(gebietInDatei.getWahl())) {
        wahlZurBearbeitungAuswaehlenAction.doIt(gebietInDatei.getWahl());
      }

      gebietSessionSelect.setEntity(gebietInDatei);

      if (gebietInDatei.equals(gebietSessionSelect.getEntity())) {
        addInfoMessage("stimmzettel.wes.import.gebiet.gewechselt", formatGebietName(gebietInDatei));
      } else {
        addErrorMessage("stimmzettel.wes.import.gebiet.wechsel.nicht.moeglich", formatGebietName(gebietInDatei));
      }
    }

    gebietsbaumView.reset();

    return FacesContextUtil.getCurrentViewId();
  }

  public void onGebietSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) GebietSelectChangeEvent event) {
    pruefeGebiet(event.getNeueEntity());
  }

  private void pruefeGebiet(Gebiet gebiet) {
    if (gebiet != null && gebiet.isErfassungsgebiet()) {
      if (!getBearbeitenGebietSecurity().isRechtVorhanden(gebiet)) {
        addWarningMessage("stimmzettel.wes.import.gebiet.bearbeiten.recht.fehlt");
      }

      if (!isStimmeingabeMoeglich(gebiet)) {
        Ergebnisart ergebnisart = gebiet.getWahl().castTo(Wahldurchfuehrung.class).getEntity().getErgebniseingangsart().korrespondierendeErgebnisart();

        Wahldurchfuehrungsgebiet gebietDasEingabeNichtZulaesst = wahldurchfuehrungsgebietExtendedDAO.findByPrimaryKey(gebiet.getPk()).getErstesGebietInHierarchieDasEingabeNichtZulaesst(ergebnisart,
            getEingangHerkunft(), getEingangsherkunftIgnorieren());

        if (gebietDasEingabeNichtZulaesst != null) {
          gebietDasEingabeNichtZulaesst.getErgebniseingabestatus(ergebnisart)
              .addWarnMessageEingabeNichtMoeglich(new ErgebniseingangExceptionContext(gebietDasEingabeNichtZulaesst, getEingangHerkunft(), gebietNummerFormatter, getMessages()));
        }
      }
    }
  }

  private BooleanSupplier getEingangsherkunftIgnorieren() {
    return () -> hasPermission(UseCasePermission.STIMMEINGABE_CREATE_EINGANGSHERKUNFT_IGNORIEREN);
  }

  private Ergebniseingangsherkunft getEingangHerkunft() {
    return Ergebniseingangsherkunft.STIMMZETTEL_ERFASSUNG;
  }

  public boolean isDateiImportierenDisabled() {
    return !isStimmzettelImportMoeglich();
  }

  @Redirect
  public String dateiImportieren() {
    WesStimmzettelAusDateiInformationen wesStimmzettelAusDateiInformationen = stimmzettelWesImportStore.getWesStimmzettelAusDateiInformationen();

    Gebiet gebietInDatei = bestimmeGebiet(wesStimmzettelAusDateiInformationen);

    if (isStimmeingabeMoeglich(gebietInDatei)) {
      stimmzettelWesImport.importiere(getMandant(), gebietInDatei, stimmzettelWesImportStore.getContent());

      addInfoMessage("stimmzettel.wes.import.success");

      if (isErgebniseingangErstellenWennMoeglich()) {
        if (stimmzettelImportErgebniseingangErzeugen.erzeugeErgebniseingangWennMoeglichUndStimmzettelVollstaendig(gebietInDatei, getErfasser(), false, getEingangsherkunftIgnorieren())) {
          addInfoMessage("stimmzettel.statusuebersicht.ergebniseingang.automatisch.angelegt");

          if (hasPermission(stimmeingabeErfassenViewResolver.getViewIdPermission())) {
            return stimmeingabeErfassenViewResolver.getViewId();
          }
        } else {
          addWarningMessage("stimmzettel.statusuebersicht.ergebniseingang.nicht.angelegt");
        }
      }

      reset();

      return goBack(StimmzettelStatusuebersichtView.VIEW_ID);
    }

    return FacesContextUtil.getCurrentViewId();
  }

  private ErgebniseingangErfasser getErfasser() {
    return ErgebniseingangErfasser.benutzer(getPrincipal());
  }

  public boolean isErgebniseingangErstellenWennMoeglich() {
    return ergebniseingangErstellenWennMoeglich;
  }

  public void setErgebniseingangErstellenWennMoeglich(boolean ergebniseingangErstellenWennMoeglich) {
    this.ergebniseingangErstellenWennMoeglich = ergebniseingangErstellenWennMoeglich;
  }
}
