
package de.ivu.elect.presentation.security;

import static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.security.PublicKey;
import java.util.List;
import java.util.Objects;

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

import org.apache.deltaspike.core.api.scope.ViewAccessScoped;
import org.primefaces.event.FileUploadEvent;
import org.primefaces.model.file.UploadedFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.CurrentCustomer;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.datei.entity.FileTypeResolver;
import de.ivu.elect.business.gebietsbaum.boundary.GebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.boundary.NlGebietDarstellungNummerFormatter;
import de.ivu.elect.business.gebietsbaum.boundary.NlGebietPublicKeyEdit;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.security.Customer;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.boundary.EmlSignKeyService;
import de.ivu.elect.business.wahl.boundary.WahlFactory;
import de.ivu.elect.business.wahl.entity.NlWahlart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.jee.faces.PreConditionCheck;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyBooleanProperty;
import de.ivu.elect.jee.presentation.dynamicform.ReadOnlyStringProperty;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.nl.x509.NlX509;
import de.ivu.elect.nl.x509.NlX509Metadata;
import de.ivu.elect.presentation.AbstractElectEditView;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.ViewHavingFileUpload;

@ViewAccessScoped
@Named(NlPublicKeyImportView.COMPONENT_NAME)
@View(viewId = NlPublicKeyImportView.VIEW_ID, useCase = UseCasePermission.CUSTOMER_1)
public class NlPublicKeyImportView extends AbstractElectEditView<Wahl> implements ViewHavingFileUpload {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "nlPublicKeyImportView";

  public static final String VIEW_ID = "/nl-public-key-import.xhtml";

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

  @Inject
  private transient FileTypeResolver fileTypeResolver;

  @Inject
  private transient WahlFactory wahlFactory;

  @Inject
  private transient NlPrivatePublicKeyNeeded nlPrivatePublicKeyNeeded;

  @Inject
  private transient GebietExtendedDAO gebietExtendedDAO;

  @Inject
  private transient CurrentCustomer currentCustomer;

  @Inject
  private transient NlGebietDarstellungNummerFormatter nlGebietDarstellungNummerFormatter;

  @Inject
  private transient NlGebietPublicKeyEdit nlGebietPublicKeyEdit;

  private UploadedFile file;

  private NlX509Metadata nlX509Metadata;

  private PublicKey publicKey;

  public NlPublicKeyImportView() {
    super();
  }

  @Nullable
  @PreConditionCheck
  public String checkPreCondition() {
    var selectedWahl = getSelectedWahl();
    var selectedGebiet = getSelectedGebiet();

    if (isPublicKeyImportZulaessig(selectedWahl, selectedGebiet, nlPrivatePublicKeyNeeded)) {
      return null;
    }

    return getStartViewId();
  }

  public static boolean isPublicKeyImportZulaessig(Wahl wahl, Gebiet gebiet, NlPrivatePublicKeyNeeded nlPrivatePublicKeyNeeded) {
    return wahl != null && nlPrivatePublicKeyNeeded.isPublicKeyNeeded(wahl.getMandant(), wahl) &&
    /* ELECTVAPP-1031 */ gebiet != null && (gebiet.isErfassungsgebiet() || /* ELECT-15308 */ NlWahlart.isHSBC(wahl) || /* ELECT-16725 */ NlWahlart.isNBSBC(wahl));
  }

  @Override
  public void handleFileUpload(FileUploadEvent event) {
    file = event.getFile();

    resetMeta();
  }

  @Override
  public String getFileUploadLabel() {
    return getMessage("eml.public.key.edit.import.pubklic.key.file", 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 Wahl getEntity() {
    return wahlFactory.create();
  }

  @Override
  protected void addProperties(FormBuilder formBuilder, Wahl doNotUse) {
    var nlX509Metadata = getNlX509Metadata();

    var wahl = getSelectedWahl();

    if (nlX509Metadata != null && wahl != null) {
      NlPublicKeyEditView.addPublicKeyInfo(formBuilder, nlX509Metadata, messages, userLocale);
      formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("eml.public.key.in.datei.vorhanden"), getPublicKey() != null));
      formBuilder.addSeparator("eml.public.key.gebiet");
      formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("eml.public.key.wahl.korrekt"), isWahlStimmtUeberein(nlX509Metadata, wahl)));

      var gebiet = findeGebiet();
      formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("eml.public.key.gebiet.gefunden"), gebiet != null));

      if (gebiet != null) {
        formBuilder.addProperty(new ReadOnlyStringProperty(getMessage("eml.public.key.gebiet.zugeordnet"), gebietNummerFormatter.getDarstellung(gebiet)));
        formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("eml.public.key.gebiet.zugeordnet.korrekt"), gebiet.equals(getSelectedGebiet())));
      }
    }
  }

  private boolean isWahlStimmtUeberein(NlX509Metadata nlX509Metadata, Wahl wahl) {
    return Objects.equals(wahl.getKennzeichen(), nlX509Metadata.getOrganisationName());
  }

  @Nullable
  private Gebiet findeGebiet() {
    var wahl = getSelectedWahl();
    var nlX509Metadata = getNlX509Metadata();

    if (file == null || nlX509Metadata == null || getPublicKey() == null || wahl == null || isBlank(nlX509Metadata.getIdentifier()) || !isWahlStimmtUeberein(nlX509Metadata, wahl)) {
      return null;
    }

    Customer customer = currentCustomer.getCustomer();

    var idPrefixKey = "";

    boolean requiresFallbackHsbc = NlWahlart.isWasHSBC(wahl, customer);
    boolean requiresFallbackNbsbc = NlWahlart.isWasNBSBC(wahl, customer);

    if (NlWahlart.isWasCSB(wahl, customer)) {
      idPrefixKey = EmlSignKeyService.EML_CERTIFICATE_ID_HSB;
    }

    if (NlWahlart.isWasHSB(wahl, customer)) {
      idPrefixKey = EmlSignKeyService.EML_CERTIFICATE_ID_GSB; // ELECT-13405, Fallback HSB => Präfix ist GSB
    }

    if (requiresFallbackNbsbc) { // ELECT-16725
      idPrefixKey = EmlSignKeyService.EML_CERTIFICATE_ID_GSB;
    }

    if (isBlank(idPrefixKey)) {
      return null;
    }

    for (var gebiet : gebietExtendedDAO.findByWahl(wahl)) {
      if (gebiet.isErfassungsgebiet() || requiresFallbackNbsbc) {
        var value = getMessage(idPrefixKey, nlGebietDarstellungNummerFormatter.formatiereNummer(gebiet));

        if (nlX509Metadata.getIdentifier().equals(value)) {
          return gebiet;
        }
      }

      if (requiresFallbackHsbc) { // ELECT-15308
        var value = getMessage(/* ELECTVAPP-2329 */ EmlSignKeyService.EML_CERTIFICATE_ID_HSB, nlGebietDarstellungNummerFormatter.formatiereNummer(gebiet));

        if (nlX509Metadata.getIdentifier().equals(value)) {
          return gebiet;
        }
      }
    }

    return null;
  }

  private NlX509Metadata getNlX509Metadata() {
    extract();
    return nlX509Metadata;
  }

  private void extract() {
    if (file == null) {
      setPublicKey(null);
      setNlX509Metadata(null);
    } else if (nlX509Metadata == null || publicKey == null) {
      try (var is = file.getInputStream()) {
        setPublicKey(new NlX509().importPublicKeyFromX509(is, this::setNlX509Metadata));
      } catch (Exception e) {
        LOG.warn("Read: " + e.getMessage(), e);

        setPublicKey(null);
        setNlX509Metadata(null);

        addErrorMessage("public.key.datei.ungueltig");
      }
    }
  }

  public void setNlX509Metadata(NlX509Metadata nlX509Metadata) {
    this.nlX509Metadata = nlX509Metadata;
  }

  public void setPublicKey(PublicKey publicKey) {
    this.publicKey = publicKey;
  }

  public PublicKey getPublicKey() {
    extract();

    return publicKey;
  }

  public void setWahlFactory(WahlFactory wahlFactory) {
    this.wahlFactory = wahlFactory;
  }

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

  @Override
  public boolean isBtnSaveRendered() {
    var gefundenesGebiet = findeGebiet();

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

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

  @Override
  @Redirect
  public String save() {
    var gebiet = findeGebiet();

    if (gebiet != null) {
      if (nlGebietPublicKeyEdit.saveGebietPublicKey(gebiet, getPublicKey(), getNlX509Metadata())) {
        addInfoMessage("eml.public.key.importiert", gebietNummerFormatter.getDarstellung(gebiet));

        reset();
      }
    }

    return cancel();
  }

  @Override
  public String getBtnSaveLabel() {
    return getMessage("eml.public.key.importieren");
  }

  @Nullable
  @Override
  @Redirect
  public String reset() {
    file = null;
    resetMeta();

    return VIEW_ID;
  }

  private void resetMeta() {
    nlX509Metadata = null;
    publicKey = null;

    resetModel();
  }

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

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

  @Override
  @Redirect
  public String cancel() {
    reset();

    var selectedGebiet = getSelectedGebiet();

    if (selectedGebiet != null && selectedGebiet.hasUebergeordnetesGebiet()) {
      gebietSessionSelect.setEntity(selectedGebiet.getUebergeordnetesGebiet()); // eine Ebene höher für den nächsten Schlüssel
    }

    return goBack(getStartViewId());
  }

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

  public String getGebietName() {
    return getSelectedGebietNameFormatted();
  }
}
