package cn.superdesk.uniorder.common.security.crypt;

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.security.crypto.codec.Utf8;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Random;


/**
 * @author linjinyu
 * @description
 * @date 2018年7月25日
 */
@Slf4j
public class SuperdeskAppPasswordUtils {

	private static final Random RANDOM = new SecureRandom();

	private static final int ITERATIONS = 10000;
	private static final int KEY_LENGTH = 256;


//	private static final String salts = DigestUtils.md5("th9sS6618c6zmm5886Mu5zCo5Votte9U");

	private static final String salts = DigestUtils.md5("cn.superdesk.app.system.dao.entity.UserEntity");

	public static String encodePassword(String rawPass, Object salt) {
		String saltedPass = mergePasswordAndSalt(rawPass, salt, false);

		MessageDigest messageDigest = getMessageDigest();

		byte[] digest = messageDigest.digest(Utf8.encode(saltedPass));

		// "stretch" the encoded value if configured to do so
		for (int i = 1; i < 1; i++) {
			digest = messageDigest.digest(digest);
		}

		return new String(Hex.encode(digest));

	}

	protected static final MessageDigest getMessageDigest() throws IllegalArgumentException {
		try {
			return MessageDigest.getInstance("SHA-1");
		} catch (NoSuchAlgorithmException e) {
			throw new IllegalArgumentException("No such algorithm [SHA-1]");
		}
	}

	protected static String mergePasswordAndSalt(String password, Object salt, boolean strict) {
		if (password == null) {
			password = "";
		}

		if (strict && (salt != null)) {
			if ((salt.toString().lastIndexOf("{") != -1) || (salt.toString().lastIndexOf("}") != -1)) {
				throw new IllegalArgumentException("Cannot use { or } in salt.toString()");
			}
		}

		if ((salt == null) || "".equals(salt)) {
			return password;
		} else {
			return password + "{" + salt.toString() + "}";
		}
	}

	/**
	 * 原来用的简单加密方式
	 *
	 * @param password
	 * @return
	 */
	public static String encryptPassword(String password) {

		StringBuilder sb = new StringBuilder();

		for (int i = 0; i < password.length(); i++) {
			sb.append(password.charAt(i)).append(salts.substring(i * 2, (i + 1) * 2));
		}

		log.info("密码{} # encryptPassword:{}", password, DigestUtils.md5(sb.toString()));

		return DigestUtils.md5(sb.toString());

	}

	/**
	 * 简单加密方式 升级版 salt 存放到 AppUser表中
	 *
	 * @param password
	 * @param salt
	 * @return
	 */
	public static String encryptPassword(String password, String salt) {


		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < password.length(); i++) {
			sb.append(password.charAt(i)).append(salt.substring(i * 2, (i + 1) * 2));
		}

		log.info("密码{} # encryptPassword:{}", password, DigestUtils.md5(sb.toString()));

		return DigestUtils.md5(sb.toString());

	}


	/**
	 * 使用说明 mark1
	 *
	 * 1. 你用getNextSalt创建一个盐
	 * 2. 您向用户询问他的密码,并使用哈希方法生成一个盐渍和散列的密码。该方法返回一个字节[],您可以使用盐保存在数据库中
	 * 3. 对用户进行身份验证,请求他的密码,从数据库中检索盐和散列密码,并使用isExpectedPassword方法检查详细信息是否匹配
	 *
	 */


	/****************************************** mark1 start ******************************************/
	/**
	 * Returns a random salt to be used to hash a password.
	 *
	 * @return a 16 bytes random salt
	 */
	public static byte[] getNextSalt() {
		byte[] salt = new byte[16];
		RANDOM.nextBytes(salt);
		return salt;
	}

	/**
	 * Returns a salted and hashed password using the provided hash.<br>
	 * Note-side effect: the password is destroyed (the char[] is filled with zeros)
	 *
	 * @param password the password to be hashed
	 * @param salt     a 16 bytes salt, ideally obtained with the getNextSalt method
	 * @return the hashed password with a pinch of salt
	 */
	public static byte[] hash(char[] password, byte[] salt) {
		PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
		Arrays.fill(password, Character.MIN_VALUE);
		try {
			SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
			return skf.generateSecret(spec).getEncoded();
		} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
			throw new AssertionError("Error while hashing a password: " + e.getMessage(), e);
		} finally {
			spec.clearPassword();
		}
	}

	/**
	 * Returns true if the given password and salt match the hashed value, false otherwise.<br>
	 * Note-side effect: the password is destroyed (the char[] is filled with zeros)
	 *
	 * @param password     the password to check
	 * @param salt         the salt used to hash the password
	 * @param expectedHash the expected hashed value of the password
	 * @return true if the given password and salt match the hashed value, false otherwise
	 */
	public static boolean isExpectedPassword(char[] password, byte[] salt, byte[] expectedHash) {
		byte[] pwdHash = hash(password, salt);
		Arrays.fill(password, Character.MIN_VALUE);
		if (pwdHash.length != expectedHash.length) return false;
		for (int i = 0; i < pwdHash.length; i++) {
			if (pwdHash[i] != expectedHash[i]) return false;
		}
		return true;
	}

	/**
	 * Generates a random password of a given length, using letters and digits.
	 *
	 * @param length the length of the password
	 * @return a random password
	 */
	public static String generateRandomPassword(int length) {
		StringBuilder sb = new StringBuilder(length);
		for (int i = 0; i < length; i++) {
			int c = RANDOM.nextInt(62);
			if (c <= 9) {
				sb.append(String.valueOf(c));
			} else if (c < 36) {
				sb.append((char) ('a' + c-10));
			} else {
				sb.append((char) ('A' + c-36));
			}
		}
		return sb.toString();
	}

	/****************************************** mark1  end  ******************************************/

	public static void main(String[] args) {

		String password = "An12345678";
		String username = "linjinyu";

		// TODO 这块密码处理只要旧系统或新系统密码中的一个匹配就可以,后期需要优化
		// 新系统加密方式
		String oldPassword = SuperdeskAppPasswordUtils.encryptPassword(password);
		// 旧系统加密方式 TODO 这块用的是旧系统的加密方式,引入了 spring-security-core 3.1.3版本,需要将加密的方法提出来,不要引入jar
		String newPassword = SuperdeskAppPasswordUtils.encodePassword(password, username);
		System.out.println(oldPassword); // 0
		System.out.println(newPassword); // 1
	}
}
