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

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.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.PasswordPropertyValueReader;
import com.airlock.iam.plugin.impl.PropertyExternalFileUtils;
import com.airlock.iam.security.util.crypto.Aes128GcmCipher;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

@Plugin(
   description = "<p>State encryption that uses AES/GCM with 128-bit keys to encrypt all values stored in the state repository.</p>",
   displayName = "AES 128 GCM State Encryption"
)
public class Aes128GcmStateEncryptionConfig implements StateEncryptionConfig {
   @StringProperty(
      description = "Key used for encryption and decryption, 128 bit encoded in Base64.<p>A random Base64 string with 128 bits (16 bytes) can be generated e.g. using openssl as follows: <tt>openssl rand -base64 16</tt></p><p><b>CAUTION:</b> Once the key is set and has been used to encrypt <em>the key must not be changed!</em>. Data encrypted with a different key cannot be recovered.</p>",
      optional = false,
      isSensitive = true,
      minLength = 8
   )
   private String secretKey;
   public static final String SECRET_KEY_PROPERTY_NAME = "secretKey";

   @ConfigurationValidator(localOnly = true)
   static void validateEncryption(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valenv) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      if (valenv != Environment.GENERAL || !PropertyExternalFileUtils.isStringInExternalFile(pc.getSimplePropertyConfigurationValue("secretKey", env, context))
         )
       {
         String secretKey = PasswordPropertyValueReader.effectivePasswordConfigValue(pc, "secretKey", env, context);

         try {
            new Aes128GcmCipher(secretKey).encrypt("Test");
         } catch (Exception e) {
            throw new PropertyConstraintViolationException(
               "secretKey", pc, env, context, "Encryption not possible with the provided secret key: " + e.getMessage()
            );
         }
      }
   }

   public String getSecretKey() {
      return this.secretKey;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public Aes128GcmStateEncryptionConfig(String secretKey) {
      this.secretKey = secretKey;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class Aes128GcmStateEncryptionConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String secretKey;

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

      @SuppressFBWarnings(justification = "generated code")
      public Aes128GcmStateEncryptionConfig.Aes128GcmStateEncryptionConfigBuilder secretKey(String secretKey) {
         this.secretKey = secretKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Aes128GcmStateEncryptionConfig build() {
         return new Aes128GcmStateEncryptionConfig(this.secretKey);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Aes128GcmStateEncryptionConfig.Aes128GcmStateEncryptionConfigBuilder(secretKey=" + this.secretKey + ")";
      }
   }
}
