
package de.ivu.elect.presentation.security;

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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Collection;

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

import org.primefaces.model.DefaultStreamedContent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.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.MessageProperty;
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.security.menu.NlPublicKeyEditMenuItem;
import de.ivu.elect.util.FileUtil;

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

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

  public static final String COMPONENT_NAME = "nlPublicKeyEditView";

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

  private static final long serialVersionUID = 1L;

  @Inject
  private transient WahlFactory wahlFactory;

  @Inject
  private transient EmlSignKeyService emlSignKeyService;

  @Inject
  private transient NlPublicKeyEditMenuItem nlPublicKeyEditMenuItem;

  @Inject
  private transient DownloadAction downloadAction;

  private String password;

  public NlPublicKeyEditView() {
    super();
  }

  @Nullable
  @PreConditionCheck
  public String checkPreCondition() {
    if (nlPublicKeyEditMenuItem.isPossible()) {
      return null;
    }

    return getStartViewId(); // wechseln des privaten Schlüssels soll nicht möglich sein
  }

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

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

  @Override
  protected void addProperties(FormBuilder formBuilder, Wahl doNotUse) {
    NlX509Metadata nlX509Metadata = emlSignKeyService.getNlX509Metadata(getSelectedWahl());

    formBuilder.addProperty(new MessageProperty(getMessage("eml.certificate.public.key.export.help")));

    addPublicKeyInfo(formBuilder, nlX509Metadata, messages, userLocale);

    formBuilder.addSeparator("eml.certificate.private.key.separator");

    File keystore = new File("<XXX>");

    try {
      keystore = emlSignKeyService.getKeyStoreFile(getSelectedWahl());
    } catch (Exception ignored) {
    }

    formBuilder.addProperty(new ReadOnlyStringProperty(getMessage("eml.certificate.label.private.key.file.path"), keystore.getAbsolutePath()));
    formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("eml.certificate.label.private.key.file.present"), keystore.isFile()));
    formBuilder.addProperty(new ReadOnlyBooleanProperty(getMessage("eml.certificate.label.private.key.valid"), isPrivateKeyValid()));

  }

  public static void addPublicKeyInfo(FormBuilder formBuilder, NlX509Metadata nlX509Metadata, Messages messages, UserLocale userLocale) {
    formBuilder.addSeparator("eml.certificate.public.key.separator");

    EmlSignKeyService.collectNlX509MetadataFormatted(messages, userLocale, nlX509Metadata, "").forEach((label, value) -> formBuilder.addProperty(new ReadOnlyStringProperty(label, value)));
  }

  private Boolean isPrivateKeyValid;

  private boolean isPrivateKeyValid() {
    if (isPrivateKeyValid == null) {
      isPrivateKeyValid = emlSignKeyService.isPrivateKeyValid(getSelectedWahl());
    }

    return isPrivateKeyValid;
  }

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

  public boolean isBtnAbrufRendered() {
    return getSelectedWahl() != null && isPrivateKeyValid();
  }

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

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

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

  @Override
  @Redirect
  public String save() {
    PrivateKey privateKey = emlSignKeyService.loadPrivateKey(getSelectedWahl());
    PublicKey publicKey = emlSignKeyService.loadPublicKey(getSelectedWahl());

    NlX509Metadata nlX509Metadata = emlSignKeyService.getNlX509Metadata(getSelectedWahl());

    try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
      new NlX509().exportPublicKeyAsX509(bos, privateKey, publicKey, nlX509Metadata);

      byte[] content = bos.toByteArray();
      String filename = createCrtFileName(nlX509Metadata);

      emlSignKeyService.downloadPublicKey(getSelectedWahl());

      DefaultStreamedContent.Builder streamedContentBuilder = DefaultStreamedContent.builder() //
          .stream(() -> new ByteArrayInputStream(content)) //
          .contentType("application/x-x509-ca-cert") //
          .name(filename) //
          .contentEncoding(StandardCharsets.UTF_8.name()) //
          .contentLength(content.length) //
      ;

      downloadAction.download(streamedContentBuilder.build());
    } catch (Exception ex) {
      LOG.warn("Fehler: {}", ex.getMessage(), ex);

      addErrorMessage("exception.unknown.message");
    } finally {
      try {
        privateKey.destroy();
      } catch (Exception ignored) {
      }
    }

    return VIEW_ID;
  }

  protected String createCrtFileName(NlX509Metadata nlX509Metadata) {
    return getMessage("eml.x509.filename", nlX509Metadata.getOrganisationName(), normalisiereTyp(nlX509Metadata.getOrganisationTypeAndRegionName())).toLowerCase();
  }

  // ELECTVAPP-1897, neue Message-Keys auch in EmlSignKeyService#bestimmeTypeAndName ergänzen!!!
  private String normalisiereTyp(String organisationTypeAndRegionName) {
    if (isBlank(organisationTypeAndRegionName)) {
      return "";
    }

    String trimmed = trimToEmpty(organisationTypeAndRegionName);

    if (trimmed.contains(" ")) {
      Collection<String> types = asList( //
          trimToEmpty(getMessage("eml.certificate.type.name.hsb.kieskring", "")), //
          trimToEmpty(getMessage("eml.certificate.type.name.psb.gemeinde", "")), //
          trimToEmpty(getMessage("eml.certificate.type.name.psb.openbaar.lichaam", "")) //
      );

      String type = types.stream().filter(value -> startsWithIgnoreCase(trimmed, value)).findFirst().orElse(trimmed.substring(0, trimmed.indexOf(" ")));
      String typeNormalized = EntityUtil.normalizeCaseAndDiacritics(type).replace(" ", "-");

      String rest = trimToEmpty(replaceOnce(trimmed, type, ""));
      String restNormalized = EntityUtil.normalizeCaseAndDiacritics(rest).replace("_", "").replace(" ", "");

      return FileUtil.normalizeFileName(typeNormalized + "-" + restNormalized);
    }

    return organisationTypeAndRegionName;
  }

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

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

  @Override
  public boolean isBtnCancelRendered() {
    return false; // ELECT-11693
  }

  @Override
  @Redirect
  public String cancel() {
    return getStartViewId();
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }
}
