package de.ivu.elect.presentation.security;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.util.Objects;

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

import org.apache.commons.lang3.StringUtils;
import org.apache.deltaspike.core.api.scope.ViewAccessScoped;
import org.primefaces.event.FileUploadEvent;
import org.primefaces.extensions.component.slideout.SlideOut;
import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.UploadedFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.CurrentCustomer;
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.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.entity.EntityUtil;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.faces.PreConditionCheck;
import de.ivu.elect.jee.faces.UserLocale;
import de.ivu.elect.jee.faces.download.DownloadAction;
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.gebietsbaum.GebietNummerFormatter;
import de.ivu.elect.presentation.security.menu.NlPublicKeyEditMenuItem;
import de.ivu.elect.util.FileUtil;

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

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

  public static final String COMPONENT_NAME = "nlPublicKeyImportView";

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

  private static final long serialVersionUID = 1L;

  @Inject
  private transient WahlFactory wahlFactory;

  @Inject
  private transient NlPrivatePublicKeyNeeded nlPrivatePublicKeyNeeded;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @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 void handleFileUpload(FileUploadEvent event) {
    file = event.getFile();
    resetMeta();
  }

  @Nullable
  @PreConditionCheck
  public String checkPreCondition() {
    Wahl selectedWahl = getSelectedWahl();
    Gebiet selectedGebiet = getSelectedGebiet();
    if (selectedWahl != null && nlPrivatePublicKeyNeeded.isPublicKeyNeeded(selectedWahl.getMandant()) &&
        /*ELECTVAPP-1031*/ selectedGebiet != null && selectedGebiet.isErfassungsgebiet()) {
      return null;
    }
    return getStartViewId();
  }

  @Override
  protected Wahl getEntity() {
    return wahlFactory.create();
  }

  @Override
  protected void addProperties(FormBuilder formBuilder, Wahl doNotUse) {
    NlX509Metadata nlX509Metadata = getNlX509Metadata();
    Wahl 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)));

      Gebiet 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
  protected Gebiet findeGebiet() {
    NlX509Metadata nlX509Metadata = getNlX509Metadata();
    Wahl wahl = getSelectedWahl();
    if (file == null || nlX509Metadata == null || getPublicKey() == null || wahl == null || StringUtils
        .isBlank(nlX509Metadata.getIdentifier()) || !isWahlstimmtUeberein(nlX509Metadata, wahl)) {
      return null;
    }

    String idPrefixKey = "";

    if (NlWahlart.isWasCSB(wahl, currentCustomer.getCustomer())) {
      idPrefixKey = EmlSignKeyService.EML_CERTIFICATE_ID_HSB;
    } else if (NlWahlart.isWasHSB(wahl, currentCustomer.getCustomer())) {
      idPrefixKey = EmlSignKeyService.EML_CERTIFICATE_ID_PSB;
    }

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

    for (Gebiet gebiet : gebietExtendedDAO.findByWahl(wahl)) {
      if (gebiet.isErfassungsgebiet()) {
        String messageForMessageKey = messages.getMessageForMessageKey(idPrefixKey, nlGebietDarstellungNummerFormatter.formatiereNummer(gebiet));
        if (nlX509Metadata.getIdentifier().equals(messageForMessageKey)) {
          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 (InputStream is = file.getInputstream()) {
        setPublicKey(new NlX509().importPublicKeyFromX509(is, this::setNlX509Metadata));
      } catch (Exception e) {
        LOG.warn("Read: " + e.getMessage(), e);
        setPublicKey(null);
        setNlX509Metadata(null);
      }
    }
  }

  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() {
    Gebiet gefundenesGebiet = findeGebiet();
    return gefundenesGebiet != null && gefundenesGebiet.equals(getSelectedGebiet());
  }

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

  @Override
  @Redirect
  public String save() {
    Gebiet 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();
    Gebiet 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();
  }

}
