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

import com.airlock.iam.common.domain.model.jwt.JwtClaimsSet;
import com.airlock.iam.common.domain.model.jwt.signature.JsonWebSignatureToken;
import com.airlock.iam.common.domain.model.jwt.signature.JwsAlgorithmHmac;
import com.airlock.iam.common.domain.service.jwt.JwtSettingsValidationError;
import com.airlock.iam.common.domain.service.jwt.signature.HmacJwtSigner;
import com.airlock.iam.common.domain.service.jwt.signature.JwtSignatureVerifier;
import com.airlock.iam.common.domain.service.jwt.signature.JwtSignatureVerifierException;
import com.airlock.iam.common.domain.service.jwt.signature.JwtSignatureVerifierImpl;
import com.airlock.iam.common.domain.service.jwt.signature.JwtSigner;
import com.airlock.iam.common.domain.service.jwt.signature.JwtSignerException;
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 an HMAC for JWTs.</p><p>This plugin can be used for JWT signature creation and verification. An HMAC is valid if the same HMAC algorithm and HMAC key are used for creation and validation.</p>",
   displayName = "JWT Ticket HMAC Settings"
)
public class JwtTicketHmacSettings implements JwtSigner, JwtSignatureVerifier {
   @EnumProperty(
      description = "The HMAC algorithm. The configured secret must be long enough for the configured hash function (e.g. >= 512 bits for HS512).",
      defaultValue = "HS512",
      optional = true,
      displayName = "HMAC Algorithm"
   )
   private JwsAlgorithmHmac hmacAlgorithm;
   private static final String HMAC_ALGORITHM_NAME = "hmacAlgorithm";
   @StringProperty(
      description = "<p>The key for the HMAC function, encoded in base64.<br/>The minimal required length depends on the configured algorithm and must be at least 256 bits. Configuration validation will fail if the secret is too short.</p><p>One can, for example, generate a random base64 string with 512 bits (64 bytes) using OpenSSL as follows: <code>openssl rand -base64 64</code></p>",
      isSensitive = true,
      optional = false,
      displayName = "HMAC Key (Base64 Encoded)"
   )
   private String hmacKey;
   private static final String HMAC_KEY_PROPERTY_NAME = "hmacKey";
   private HmacJwtSigner ticketSigner;
   private JwtSignatureVerifier ticketVerifier;

   public JwtTicketHmacSettings(JwsAlgorithmHmac hmacAlgorithm, String hmacKey) {
      this.hmacAlgorithm = hmacAlgorithm;
      this.hmacKey = hmacKey;
   }

   @ConfigurationValidator
   public static void ensureHmacAlgorithmAndKeyWork(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valenv) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      if (valenv == Environment.CURRENT_VM) {
         JwsAlgorithmHmac hmacAlgorithm = (JwsAlgorithmHmac)EnumPropertyValueReader.effectiveConfigEnum(pc, "hmacAlgorithm", env, context);
         String hmacKey = StringPropertyValueReader.effectiveStringConfigValue(pc, "hmacKey", env, context);
         Optional<JwtSettingsValidationError> signerError = HmacJwtSigner.validate(hmacAlgorithm, hmacKey);
         Optional<JwtSettingsValidationError> verifierError = JwtSignatureVerifierImpl.validate(hmacAlgorithm, hmacKey);
         if (signerError.isPresent() || verifierError.isPresent()) {
            JwtSettingsValidationError error = signerError.orElseGet(verifierError::get);
            throw new PropertyConstraintViolationException("hmacKey", pc, env, context, error.getMessage(), error.getCause().orElse(null));
         }
      }
   }

   private static HmacJwtSigner buildSigner(JwsAlgorithmHmac hmacAlgorithm, String hmacKey) {
      return HmacJwtSigner.builder().hmacAlgorithm(hmacAlgorithm).base64EncodedHmacKey(hmacKey).build();
   }

   private static JwtSignatureVerifier buildVerifier(JwsAlgorithmHmac hmacAlgorithm, String hmacKey) {
      return JwtSignatureVerifierImpl.builder().hmacAlgorithm(hmacAlgorithm).hmacKey(hmacKey).build();
   }

   @AfterConfigActivation
   void afterConfigActivation() {
      this.ticketSigner = buildSigner(this.hmacAlgorithm, this.hmacKey);
      this.ticketVerifier = buildVerifier(this.hmacAlgorithm, this.hmacKey);
   }

   @Override
   public boolean isSignatureValid(JsonWebSignatureToken signedJwt) throws JwtSignatureVerifierException {
      return Objects.requireNonNull(this.ticketVerifier, "AfterConfigActivation has not been called").isSignatureValid(signedJwt);
   }

   @Override
   public JsonWebSignatureToken sign(JwtClaimsSet payload) throws JwtSignerException {
      return Objects.requireNonNull(this.ticketSigner, "AfterConfigActivation has not been called").sign(payload);
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class JwtTicketHmacSettingsBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private JwsAlgorithmHmac hmacAlgorithm;
      @SuppressFBWarnings(justification = "generated code")
      private String hmacKey;

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

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketHmacSettings.JwtTicketHmacSettingsBuilder hmacAlgorithm(JwsAlgorithmHmac hmacAlgorithm) {
         this.hmacAlgorithm = hmacAlgorithm;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketHmacSettings.JwtTicketHmacSettingsBuilder hmacKey(String hmacKey) {
         this.hmacKey = hmacKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public JwtTicketHmacSettings build() {
         return new JwtTicketHmacSettings(this.hmacAlgorithm, this.hmacKey);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "JwtTicketHmacSettings.JwtTicketHmacSettingsBuilder(hmacAlgorithm=" + this.hmacAlgorithm + ", hmacKey=" + this.hmacKey + ")";
      }
   }
}
