package com.airlock.iam.common.application.configuration.jwt;

import com.airlock.iam.common.api.application.configuration.jwt.JWKPairConfig;
import com.airlock.iam.core.misc.util.crypto.keystore.KeystoreConfig;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.lifecycle.AfterConfigActivation;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.JWK;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.util.Optional;

@Plugin(displayName = "Public/Private JWK Configuration", description = "Configuration for retrieving a public/private JWK, stored in a keystore.")
public class JWKPairFromKeyStoreConfig implements JWKPairConfig {
   @PluginProperty(description = "The Keystore containing the corresponding private/public keypair.", optional = false)
   private KeystoreConfig keyStore;
   @StringProperty(description = "Entry name of the Public/Private Keypair in the keystore.", optional = false)
   private String keyPairAlias;
   @StringProperty(
      description = "Password for decrypting the private key configured above. Can be left empty if the private key was not encrypted.",
      optional = true,
      isSensitive = true
   )
   private String privateKeyPassword;
   private JWK keyPair;

   public JWKPairFromKeyStoreConfig(KeystoreConfig keyStore, String keyPairAlias, String privateKeyPassword) {
      this.keyStore = keyStore;
      this.keyPairAlias = keyPairAlias;
      this.privateKeyPassword = privateKeyPassword;
   }

   @AfterConfigActivation
   void loadKeys() throws InitializeException {
      KeyStore loadedKeyStore = this.keyStore.loadKeystore();
      Optional<String> optPrivateKeyPassword = Optional.ofNullable(this.privateKeyPassword);

      try {
         this.keyPair = loadKeyPair(loadedKeyStore, this.keyPairAlias, optPrivateKeyPassword);
      } catch (RuntimeException e) {
         throw new InitializeException("Error while loading JWK key pair.", e);
      }
   }

   private static JWK loadKeyPair(KeyStore keyStore, String alias, Optional<String> password) {
      try {
         JWK jwk = JWK.load(keyStore, alias, password.orElse("").toCharArray());
         if (jwk == null) {
            throw new RuntimeException(String.format("Could not find the key (%s) in the configured key store.", alias));
         } else {
            return jwk;
         }
      } catch (KeyStoreException | JOSEException e) {
         throw new RuntimeException(String.format("Could not load the key (%s) from the configured key store: %s.", alias, e.getMessage()), e);
      }
   }

   @Override
   public JWK getKeyPair() {
      return this.keyPair;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static JWKPairFromKeyStoreConfig.JWKPairFromKeyStoreConfigBuilder builder() {
      return new JWKPairFromKeyStoreConfig.JWKPairFromKeyStoreConfigBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public JWKPairFromKeyStoreConfig() {
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class JWKPairFromKeyStoreConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private KeystoreConfig keyStore;
      @SuppressFBWarnings(justification = "generated code")
      private String keyPairAlias;
      @SuppressFBWarnings(justification = "generated code")
      private String privateKeyPassword;

      @SuppressFBWarnings(justification = "generated code")
      JWKPairFromKeyStoreConfigBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public JWKPairFromKeyStoreConfig.JWKPairFromKeyStoreConfigBuilder keyStore(KeystoreConfig keyStore) {
         this.keyStore = keyStore;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JWKPairFromKeyStoreConfig.JWKPairFromKeyStoreConfigBuilder keyPairAlias(String keyPairAlias) {
         this.keyPairAlias = keyPairAlias;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JWKPairFromKeyStoreConfig.JWKPairFromKeyStoreConfigBuilder privateKeyPassword(String privateKeyPassword) {
         this.privateKeyPassword = privateKeyPassword;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JWKPairFromKeyStoreConfig build() {
         return new JWKPairFromKeyStoreConfig(this.keyStore, this.keyPairAlias, this.privateKeyPassword);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "JWKPairFromKeyStoreConfig.JWKPairFromKeyStoreConfigBuilder(keyStore="
            + this.keyStore
            + ", keyPairAlias="
            + this.keyPairAlias
            + ", privateKeyPassword="
            + this.privateKeyPassword
            + ")";
      }
   }
}
