package com.airlock.iam.security.util.crypto;

import com.airlock.iam.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.security.util.SeededSecureRandom;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.util.Optional;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;

public final class Aes128GcmCipher implements IamCipher {
   private static final ELogger LOG = ELoggerFactory.getLogger(Aes128GcmCipher.class);
   private static final int IV_LENGTH = 12;
   private static final SecureRandom RANDOM = SeededSecureRandom.getInstance();
   private final SecretKeySpec secretKey;
   private final javax.crypto.Cipher cipher;

   public Aes128GcmCipher(String base64Key) {
      this.secretKey = new SecretKeySpec(Base64.decodeBase64(base64Key), "AES");

      try {
         this.cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding");
      } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
         throw new RuntimeException("Could not initialize encryption cipher", e);
      }
   }

   @Override
   public String encrypt(String plaintext) {
      byte[] iv = new byte[12];
      RANDOM.nextBytes(iv);
      GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);

      try {
         this.cipher.init(1, this.secretKey, gcmParameterSpec);
         byte[] encryptedData = this.cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
         byte[] result = ArrayUtils.addAll(iv, encryptedData);
         return new String(java.util.Base64.getEncoder().encode(result), StandardCharsets.UTF_8);
      } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
         throw new RuntimeException("Could not encrypt", e);
      }
   }

   @Override
   public Optional<String> decrypt(String ciphertext) {
      byte[] encryptedData;
      try {
         encryptedData = java.util.Base64.getDecoder().decode(ciphertext);
      } catch (IllegalArgumentException e) {
         LOG.warn(EDefaultLogType.GENERIC_LOG_TYPE, "String to decrypt is not a valid Base64 encoded ciphertext.");
         return Optional.empty();
      }

      if (encryptedData.length < 12) {
         LOG.warn(EDefaultLogType.GENERIC_LOG_TYPE, "String to decrypt is too short to be a ciphertext.");
         return Optional.empty();
      }

      byte[] iv = new byte[12];
      System.arraycopy(encryptedData, 0, iv, 0, iv.length);
      GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);

      try {
         this.cipher.init(2, this.secretKey, gcmParameterSpec);
         byte[] decryptedData = this.cipher.doFinal(encryptedData, 12, encryptedData.length - 12);
         return Optional.of(new String(decryptedData, StandardCharsets.UTF_8));
      } catch (AEADBadTagException | ProviderException e) {
         LOG.warn(EDefaultLogType.GENERIC_LOG_TYPE, "Integrity violation detected for ciphertext: " + e.getMessage());
         return Optional.empty();
      } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
         LOG.warn(EDefaultLogType.GENERIC_LOG_TYPE, "Could not decrypt ciphertext: " + e.getMessage());
         return Optional.empty();
      }
   }
}
