package de.ivu.elect.jee.security;

import java.io.Serializable;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.Random;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.util.ElectRandom;

public class PasswordEncryptionServiceSHA512 implements PasswordEncryptionService, Serializable {

  private static final Logger LOG = LoggerFactory.getLogger(PasswordEncryptionServiceSHA512.class);

  private static final long serialVersionUID = 1L;

  @Inject
  private transient PasswordEncryptionServiceSHA512Iterations passwordEncryptionServiceSHA512Iterations;

  @Override
  public boolean authenticate(String clearTextPassword, String encryptedPasswordAsBase64EncodedString, String saltAsBase64EncodedString) {
    return authenticate(
        clearTextPassword,
        decodePassword(encryptedPasswordAsBase64EncodedString),
        fromBase64CodedString(saltAsBase64EncodedString));
  }

  private byte[] decodePassword(String encryptedPasswordAsBase64EncodedString) {
    return fromBase64CodedString(
            encryptedPasswordAsBase64EncodedString.substring(0, encryptedPasswordAsBase64EncodedString.length() - SHA512.ENCRYPTED_MARKER_SUFFIX.length()));
  }

  private boolean authenticate(String attemptedPassword, byte[] encryptedPassword, byte[] salt) {
    // Encrypt the clear-text password using the same salt that was used to
    // encrypt the original password
    long start = System.currentTimeMillis();

    int iterations = getIterations();
    byte[] encryptedAttemptedPassword = SHA512.getEncryptedPassword(attemptedPassword, salt, iterations);

    LOG.info("hash time: " + (System.currentTimeMillis() - start) + "; iterations=" + iterations);

    // Authentication succeeds if encrypted password that the user entered
    // is equal to the stored hash

    return MessageDigest.isEqual(encryptedPassword, encryptedAttemptedPassword);
  }

  protected int getIterations(){
    return passwordEncryptionServiceSHA512Iterations.getIterations();
  }

  private byte[] generateSalt() {
    // VERY important to use SecureRandom instead of just Random

    // https://stackoverflow.com/questions/27622625/securerandom-with-nativeprng-vs-sha1prng
    // ELECT-8368 SecureRandom random = SecureRandom.getInstanceStrong();
    Random random = ElectRandom.getRandom();

    // Generate a 64 byte (512bit)
    byte[] salt = new byte[64];
    random.nextBytes(salt);

    return salt;
  }


  @Override
  public String getEncryptedPasswordAsBase64EncodedString(String password, String saltAsBase64EncodedString) {
    return getBase64PasswordString(
        toBase64CodedString(SHA512.getEncryptedPassword(password, fromBase64CodedString(saltAsBase64EncodedString),
            getIterations())));
  }

  private String getBase64PasswordString(String base64CodedString) {
    return base64CodedString + SHA512.ENCRYPTED_MARKER_SUFFIX;
  }

  @Override
  public String generateSaltAsBase64EncodedString() {
    return toBase64CodedString(generateSalt());
  }

  private String toBase64CodedString(byte[] arr) {
    return Base64.getEncoder().encodeToString(arr);
  }

  private byte[] fromBase64CodedString(String str) {
    return Base64.getDecoder().decode(str);
  }

  @Override
  public boolean isEncrypted(String passwort) {
    try {
      return passwort.endsWith(SHA512.ENCRYPTED_MARKER_SUFFIX) && decodePassword(passwort) != null;
    } catch (Exception e) {
      return false;
    }
  }

}
