package com.airlock.iam.common.domain.service.jwt.signature;

import com.airlock.iam.common.domain.model.jwt.JwtClaimsSet;
import com.airlock.iam.common.domain.model.jwt.signature.HmacAlgorithm;
import com.airlock.iam.common.domain.model.jwt.signature.JsonWebSignatureToken;
import com.airlock.iam.common.domain.model.jwt.signature.JwsAlgorithm;
import com.airlock.iam.common.domain.service.jwt.JwtSettingsValidationError;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.plugin.api.config.LogTypes;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.KeyLengthException;
import com.nimbusds.jose.JWSHeader.Builder;
import com.nimbusds.jose.crypto.MACSigner;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Arrays;
import java.util.Optional;
import org.apache.commons.codec.binary.Base64;

public class HmacJwtSigner implements JwtSigner {
   private static final ELogger LOG = ELoggerFactory.getLogger(HmacJwtSigner.class);
   private final JwsAlgorithm jwsAlgorithm;
   private final JwsSignerWrapper signer;

   public static Optional<JwtSettingsValidationError> validate(HmacAlgorithm hmacAlgorithm, String base64EncodedHmacKey) {
      Optional<JwtSettingsValidationError> jwtSettingsValidationError = trySignerCreation(base64EncodedHmacKey);
      if (jwtSettingsValidationError.isPresent()) {
         return jwtSettingsValidationError;
      }

      JwsSignerWrapper signerWrapper = hmacSigner(Base64.decodeBase64(base64EncodedHmacKey));
      return signerWrapper.validateAlgorithmSupported(hmacAlgorithm);
   }

   private static Optional<JwtSettingsValidationError> trySignerCreation(String base64EncodedHmacKey) {
      try {
         new MACSigner(Base64.decodeBase64(base64EncodedHmacKey));
         return Optional.empty();
      } catch (KeyLengthException e) {
         return Optional.of(new JwtSettingsValidationError(e.getMessage(), Optional.of(e)));
      }
   }

   private static JwsSignerWrapper hmacSigner(byte[] secret) {
      try {
         return JwsSignerWrapper.signerWrapper(new MACSigner(secret));
      } catch (KeyLengthException e) {
         LOG.warn(LogTypes.CONFIGURATION_ERROR, "Cannot create MAC signature, probably the secret is too short? Cause: " + e.getMessage());
         throw new IllegalArgumentException("Cannot create MAC signature", e);
      }
   }

   public HmacJwtSigner(HmacAlgorithm hmacAlgorithm, String base64EncodedHmacKey, byte[] hmacKey) {
      this.jwsAlgorithm = hmacAlgorithm;
      this.signer = this.createHmacSignerWrapper(base64EncodedHmacKey, hmacKey);
      this.assertAlgorithmSupported(hmacAlgorithm);
   }

   private JwsSignerWrapper createHmacSignerWrapper(String base64EncodedHmacKey, byte[] hmacKey) {
      if (base64EncodedHmacKey != null && hmacKey != null) {
         throw new IllegalArgumentException("The key must be provided either in base64 or as byte array.");
      } else {
         return base64EncodedHmacKey != null ? hmacSigner(Base64.decodeBase64(base64EncodedHmacKey)) : hmacSigner(hmacKey);
      }
   }

   private void assertAlgorithmSupported(HmacAlgorithm hmacAlgorithm) {
      this.signer.validateAlgorithmSupported(hmacAlgorithm).ifPresent(error -> {
         throw new IllegalArgumentException(error.getMessage());
      });
   }

   @Override
   public JsonWebSignatureToken sign(JwtClaimsSet payload) throws JwtSignerException {
      JsonWebSignatureToken token = this.signer.sign(this.jwtHeader(), payload);
      LOG.debug("Successfully signed the JWT.");
      return token;
   }

   private JWSHeader jwtHeader() {
      return new Builder(JWSAlgorithm.parse(this.jwsAlgorithm.name())).type(JOSEObjectType.JWT).build();
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class HmacJwtSignerBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private HmacAlgorithm hmacAlgorithm;
      @SuppressFBWarnings(justification = "generated code")
      private String base64EncodedHmacKey;
      @SuppressFBWarnings(justification = "generated code")
      private byte[] hmacKey;

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

      @SuppressFBWarnings(justification = "generated code")
      public HmacJwtSigner.HmacJwtSignerBuilder hmacAlgorithm(HmacAlgorithm hmacAlgorithm) {
         this.hmacAlgorithm = hmacAlgorithm;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public HmacJwtSigner.HmacJwtSignerBuilder base64EncodedHmacKey(String base64EncodedHmacKey) {
         this.base64EncodedHmacKey = base64EncodedHmacKey;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public HmacJwtSigner.HmacJwtSignerBuilder hmacKey(byte[] hmacKey) {
         this.hmacKey = hmacKey;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "HmacJwtSigner.HmacJwtSignerBuilder(hmacAlgorithm="
            + this.hmacAlgorithm
            + ", base64EncodedHmacKey="
            + this.base64EncodedHmacKey
            + ", hmacKey="
            + Arrays.toString(this.hmacKey)
            + ")";
      }
   }
}
