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

import com.airlock.iam.common.domain.model.jwt.encryption.DirectEncryptionMethod;
import com.airlock.iam.common.domain.model.jwt.encryption.JweToken;
import com.airlock.iam.common.domain.model.jwt.signature.JsonWebSignatureToken;
import com.airlock.iam.common.domain.service.jwt.JwtSettingsValidationError;
import com.airlock.iam.common.domain.service.jwt.encryption.JwtTicketDecrypter;
import com.airlock.iam.common.domain.service.jwt.encryption.JwtTicketDirectAesEncrypterDecrypter;
import com.airlock.iam.common.domain.service.jwt.encryption.JwtTicketEncrypter;
import com.airlock.iam.core.misc.util.ticket.TicketEncodingException;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
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.EnumProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.EnumPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Objects;
import java.util.Optional;

@Plugin(
   description = "<p>Configures symmetric direct encryption for JWTs.</p><p>This plugin can be used for JWT encryption and decryption. To decrypt a JWT the same algorithm and key must be used as for encryption.</p>",
   displayName = "JWT Ticket Direct AES Encryption Settings"
)
public class JwtTicketDirectAesEncryptionSettings implements JwtTicketEncrypter, JwtTicketDecrypter {
   @EnumProperty(
      description = "The algorithm used for direct encryption. The configured secret must match the length requirements for the configured direct encryption method, see property 'Encryption Key'.",
      defaultValue = "A256GCM",
      optional = true
   )
   private DirectEncryptionMethod directEncryptionMethod;
   public static final String DIRECT_ENCRYPTION_METHOD_NAME = "directEncryptionMethod";
   @StringProperty(
      description = "<p>The key for the selected direct encryption method, encoded in base64. The minimal required length depends on the configured encryption method and must be chosen as follows: <ul><li> A128GCM: 128 bits / 16 bytes </li><li> A192GCM: 192 bits / 24 bytes </li><li> A256GCM: 256 bits / 32 bytes </li><li> A128CBC_HS256: 256 bits / 32 bytes </li><li> A192CBC_HS384: 384 bits / 48 bytes </li><li> A256CBC_HS512: 512 bits / 64 bytes </li></ul></p><p>One can, for example, generate a random base64 string with 256 bits (32 bytes) using OpenSSL as follows: <code>openssl rand -base64 32</code></p>",
      isSensitive = true,
      optional = false,
      displayName = "Encryption Key (Base64 Encoded)"
   )
   private String encryptionKey;
   public static final String ENCRYPTION_KEY_PROPERTY_NAME = "encryptionKey";
   private JwtTicketDirectAesEncrypterDecrypter ticketEncryption;

   public JwtTicketDirectAesEncryptionSettings(DirectEncryptionMethod directEncryptionMethod, String encryptionKey) {
      this.directEncryptionMethod = directEncryptionMethod;
      this.encryptionKey = encryptionKey;
   }

   @ConfigurationValidator(localOnly = true)
   static void ensureEncryptionMethodAndKeyWork(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valenv) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      if (valenv == Environment.CURRENT_VM) {
         DirectEncryptionMethod encryptionMethod = (DirectEncryptionMethod)EnumPropertyValueReader.effectiveConfigEnum(
            pc, "directEncryptionMethod", env, context
         );
         String encryptionKey = StringPropertyValueReader.effectiveStringConfigValue(pc, "encryptionKey", env, context);
         Optional<JwtSettingsValidationError> error = JwtTicketDirectAesEncrypterDecrypter.validate(encryptionMethod, encryptionKey);
         if (error.isPresent()) {
            throw new PropertyConstraintViolationException("encryptionKey", pc, env, context, error.get().getMessage(), error.get().getCause().orElse(null));
         }
      }
   }

   @ConfigurationValidator(localOnly = true)
   static void validateEncryption(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valenv) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      if (valenv == Environment.CURRENT_VM) {
         String encryptionKey = StringPropertyValueReader.effectiveStringConfigValue(pc, "encryptionKey", env, context);
         DirectEncryptionMethod directEncryptionMethod = (DirectEncryptionMethod)EnumPropertyValueReader.effectiveConfigEnum(
            pc, "directEncryptionMethod", env, context
         );

         try {
            JwtTicketDirectAesEncrypterDecrypter.builder().directEncryptionMethod(directEncryptionMethod).encryptionKey(encryptionKey).build();
         } catch (Exception e) {
            throw new PropertyConstraintViolationException(
               "directEncryptionMethod",
               pc,
               env,
               context,
               "Encryption not possible with the provided method and encryption key. Maybe the key is no valid Base64 string?"
            );
         }
      }
   }

   @AfterConfigActivation
   void afterConfigActivation() {
      this.ticketEncryption = JwtTicketDirectAesEncrypterDecrypter.builder()
         .directEncryptionMethod(this.directEncryptionMethod)
         .encryptionKey(this.encryptionKey)
         .build();
   }

   @Override
   public JsonWebSignatureToken decrypt(JweToken jweToken) throws TicketEncodingException {
      return Objects.requireNonNull(this.ticketEncryption, "AfterConfigActivation has not been called").decrypt(jweToken);
   }

   @Override
   public JweToken encrypt(JsonWebSignatureToken payload) throws TicketEncodingException {
      return Objects.requireNonNull(this.ticketEncryption, "AfterConfigActivation has not been called").encrypt(payload);
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class JwtTicketDirectAesEncryptionSettingsBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private DirectEncryptionMethod directEncryptionMethod;
      @SuppressFBWarnings(justification = "generated code")
      private String encryptionKey;

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

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDirectAesEncryptionSettings.JwtTicketDirectAesEncryptionSettingsBuilder directEncryptionMethod(
         DirectEncryptionMethod directEncryptionMethod
      ) {
         this.directEncryptionMethod = directEncryptionMethod;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDirectAesEncryptionSettings.JwtTicketDirectAesEncryptionSettingsBuilder encryptionKey(String encryptionKey) {
         this.encryptionKey = encryptionKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketDirectAesEncryptionSettings build() {
         return new JwtTicketDirectAesEncryptionSettings(this.directEncryptionMethod, this.encryptionKey);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "JwtTicketDirectAesEncryptionSettings.JwtTicketDirectAesEncryptionSettingsBuilder(directEncryptionMethod="
            + this.directEncryptionMethod
            + ", encryptionKey="
            + this.encryptionKey
            + ")";
      }
   }
}
