package com.style24.core.support.util;

import java.io.UnsupportedEncodingException;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * 암호화 Util Class
 * @author gagamel
 * @since 2021. 1. 22
 */
@Slf4j
public class CryptoUtils {

	// AES키
	private static final String AES_KEY = "dPtm24dnpqtjqltmdkaghsmsqlalf!@#";
	private static final byte[] IV_BYTES = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

	/**
	 * OLD
	 * @param rawValue - 원시문자열
	 * @return String - 암호화문자열
	 */
	public static String oldEncryptAES(String rawValue) {
		String encryptedValue = "";

		try {
			if (StringUtils.isNotBlank(rawValue)) {
				byte[] keyBytes = AES_KEY.getBytes("UTF-8");
				SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
				Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
				cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(IV_BYTES));
				encryptedValue = Base64.encodeBase64String(cipher.doFinal(rawValue.getBytes("UTF-8")));
			}
		} catch (Exception e) {
			//log.error(e.getMessage());
			encryptedValue = rawValue;
		}

		return encryptedValue;
	}

	/**
	 * OLD
	 * @param encryptedValue - 암호화된 문자열
	 * @return String - 복호화문자열
	 */
	public static String oldDecryptAES(String encryptedValue) {
		String decryptedValue = "";

		try {
			if (StringUtils.isNotBlank(encryptedValue)) {
				byte[] keyBytes = AES_KEY.getBytes("UTF-8");
				SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
				Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
				cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(IV_BYTES));
				decryptedValue = new String(cipher.doFinal(Base64.decodeBase64(encryptedValue.getBytes("UTF-8"))), "UTF-8");
				if (StringUtils.isBlank(decryptedValue)) {
					decryptedValue = encryptedValue;
				}
			}
		} catch (Exception e) {
			//log.error(e.getMessage());
			decryptedValue = encryptedValue;
		}

		return decryptedValue;
	}

	/**
	 * YES24용 AES 암호화 처리
	 * @param rawValue - 원시문자열
	 * @return String - 암호화문자열
	 */
	public static String encryptYes24AES(String rawValue) {
		String encryptedValue = "";

		try {
			if (StringUtils.isNotBlank(rawValue)) {
				byte[] keyBytes = AES_KEY.getBytes("UTF-8");
				SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
				Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
				cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(IV_BYTES));
				encryptedValue = Base64.encodeBase64String(cipher.doFinal(rawValue.getBytes("UTF-8")));
			}
		} catch (Exception e) {
			//log.error(e.getMessage());
			encryptedValue = rawValue;
		}

		return encryptedValue;
	}

	/**
	 * YES24 연동용 AES 복호화 처리
	 * @param encryptedValue - 암호화된 문자열
	 * @return String - 복호화문자열
	 */
	public static String decryptYes24AES(String encryptedValue) {
		String decryptedValue = "";

		try {
			if (StringUtils.isNotBlank(encryptedValue)) {
				byte[] keyBytes = AES_KEY.getBytes("UTF-8");
				SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
				Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
				cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(IV_BYTES));
				decryptedValue = new String(cipher.doFinal(Base64.decodeBase64(encryptedValue.getBytes("UTF-8"))), "UTF-8");
				if (StringUtils.isBlank(decryptedValue)) {
					decryptedValue = encryptedValue;
				}
			}
		} catch (Exception e) {
			//log.error(e.getMessage());
			decryptedValue = encryptedValue;
		}

		return decryptedValue;
	}

	/**
	 * JAVA에서는 AES키는 16byte이지만, MySQL에서는 16byte 이상도 지원하기에 MySQL의 16byte 이상의 키를 생성해야 한다.
	 * 16byte 이상은 잘라서 다시 앞쪽부터 byte 단위로 XOR 시켜서 16byte 키로 생성
	 * @param encoding
	 * @return
	 */
	private static SecretKeySpec generateMysqlAESKey(final String encoding) {
		try {
			final byte[] finalKey = new byte[16];
			int i = 0;
			for (byte b : AES_KEY.getBytes(encoding)) {
				finalKey[i++ % 16] ^= b;
			}
			return new SecretKeySpec(finalKey, "AES");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * MySQL AES128 암호화 처리
	 * 		MySQL) HEX(AES_ENCRYPT('테스트', AES_KEY))
	 * @param rawValue - 원시문자열
	 * @return String - 암호화문자열
	 */
	public static String encryptAES(String rawValue) {
		String encryptedValue = "";

		try {
			if (StringUtils.isNotBlank(rawValue)) {
				final Cipher cipher = Cipher.getInstance("AES"); // Cipher.getInstance("AES/ECB/PKCS5Padding")와 같음. ECB 모드의 경우 cipher.init 시 IvParameterSpec을 넣음 안 된다.
				cipher.init(Cipher.ENCRYPT_MODE, generateMysqlAESKey("UTF-8"));
				encryptedValue = new String(Hex.encodeHex(cipher.doFinal(rawValue.getBytes("UTF-8")))).toUpperCase();
			}
		} catch (Exception e) {
//			log.error(e.getMessage());
			encryptedValue = rawValue;
		}

		return encryptedValue;
	}

	/**
	 * MySQL AES128 복호화 처리
	 * 		MySQL) CAST(AES_DECRYPT(UNHEX('0EF41E8ED0A5D40D2F8DC4C0A3A9C722'),AES_KEY) AS CHAR)
	 * @param encryptedValue - 암호화된 문자열
	 * @return String - 복호화문자열
	 */
	public static String decryptAES(String encryptedValue) {
		String decryptedValue = "";

		try {
			if (StringUtils.isNotBlank(encryptedValue)) {
				final Cipher cipher = Cipher.getInstance("AES"); // Cipher.getInstance("AES/ECB/PKCS5Padding")와 같음
				cipher.init(Cipher.DECRYPT_MODE, generateMysqlAESKey("UTF-8"));
				decryptedValue = new String(cipher.doFinal(Hex.decodeHex(encryptedValue.toCharArray())), "UTF-8");
//				if (StringUtils.isBlank(decryptedValue)) {
//					decryptedValue = encryptedValue;
//				}
			}
		} catch (Exception e) {
//			log.error(e.getMessage());
			decryptedValue = encryptedValue;
		}

		return decryptedValue;
	}

	public static void main(String[] args) throws Exception {
		String txt = "테스트";
		String encrypt = CryptoUtils.encryptAES(txt);
		String decrypt = CryptoUtils.decryptAES(encrypt);

		log.info("평문 : {}", txt);
		log.info("암호화 : {}", encrypt);
		log.info("복호화 : {}", decrypt);
	}

}
