package com.airlock.iam.common.api.domain.model.binary;

import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;

public final class ByteSequence {
   private static final ELogger LOG = ELoggerFactory.getLogger(ByteSequence.class);
   private static final Encoder BASE64URL_ENCODER = Base64.getUrlEncoder().withoutPadding();
   private static final Decoder BASE64URL_DECODER = Base64.getUrlDecoder();
   private static final int MAX_NUMBER_OF_CHARACTERS_FOR_LOGGING = 30;
   private static final String TRUNCATED_SYMBOL = "...";
   private static final HashFunction SHA_512 = Hashing.sha512();
   private static final BaseEncoding HEXADECIMAL_ENCODING = BaseEncoding.base16().lowerCase();
   private final byte[] decodedBytes;
   private final String base64Url;

   private ByteSequence(byte @NonNull [] decodedBytes) {
      if (decodedBytes == null) {
         throw new NullPointerException("decodedBytes is marked non-null but is null");
      }

      this.decodedBytes = Arrays.copyOf(decodedBytes, decodedBytes.length);
      this.base64Url = BASE64URL_ENCODER.encodeToString(this.decodedBytes);
   }

   public static ByteSequence byteSequence(byte[] decodedBytes) {
      return new ByteSequence(decodedBytes);
   }

   public static Optional<ByteSequence> fromBase64Url(String base64Url) {
      try {
         return Optional.of(new ByteSequence(BASE64URL_DECODER.decode(base64Url)));
      } catch (IllegalArgumentException e) {
         LOG.debug(String.format("The string is not properly Base64URL encoded. Cause: %s", e.getMessage()));
         return Optional.empty();
      }
   }

   public String base64Url() {
      return this.base64Url;
   }

   public String base64UrlTruncated(int maxNumberOfCharacters) {
      if (this.base64Url.length() <= maxNumberOfCharacters) {
         return this.base64Url;
      } else {
         return maxNumberOfCharacters <= "...".length()
            ? StringUtils.truncate(this.base64Url, maxNumberOfCharacters)
            : StringUtils.truncate(this.base64Url, maxNumberOfCharacters - "...".length()) + "...";
      }
   }

   public String base64UrlTruncated() {
      return this.base64UrlTruncated(30);
   }

   public byte[] bytes() {
      return Arrays.copyOf(this.decodedBytes, this.decodedBytes.length);
   }

   public ByteSequence sha512() {
      return byteSequence(SHA_512.hashBytes(this.bytes()).asBytes());
   }

   public String hex() {
      return HEXADECIMAL_ENCODING.encode(this.decodedBytes);
   }

   public int length() {
      return this.decodedBytes.length;
   }

   @Override
   public String toString() {
      return this.base64UrlTruncated();
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public boolean equals(Object o) {
      if (o == this) {
         return true;
      } else if (!(o instanceof ByteSequence other)) {
         return false;
      } else {
         if (!Arrays.equals(this.decodedBytes, other.decodedBytes)) {
            return false;
         }

         Object this$base64Url = this.base64Url;
         Object other$base64Url = other.base64Url;
         return this$base64Url == null ? other$base64Url == null : this$base64Url.equals(other$base64Url);
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public int hashCode() {
      int PRIME = 59;
      int result = 1;
      result = result * 59 + Arrays.hashCode(this.decodedBytes);
      Object $base64Url = this.base64Url;
      return result * 59 + ($base64Url == null ? 43 : $base64Url.hashCode());
   }
}
