package com.oqpo.api.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

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

import org.apache.commons.codec.binary.Hex;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.util.Assert;

public class CryptoUtil {
	public static String decrypt(String ciphertext, String passphrase) {
        try {
            final int keySize = 256;
            final int ivSize = 128;
            byte[] ctBytes = Base64.decodeBase64(ciphertext.getBytes("UTF-8"));
            byte[] saltBytes = Arrays.copyOfRange(ctBytes, 8, 16);
            byte[] ciphertextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.length);
            byte[] key = new byte[keySize / 8];
            byte[] iv = new byte[ivSize / 8];
            EvpKDF(passphrase.getBytes("UTF-8"), keySize, ivSize, saltBytes, key, iv);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
            byte[] recoveredPlaintextBytes = cipher.doFinal(ciphertextBytes);
            return new String(recoveredPlaintextBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static byte[] EvpKDF(byte[] password, int keySize, int ivSize, byte[] salt, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
        return EvpKDF(password, keySize, ivSize, salt, 1, "MD5", resultKey, resultIv);
    }

    private static byte[] EvpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
        keySize = keySize / 32;
        ivSize = ivSize / 32;
        int targetKeySize = keySize + ivSize;
        byte[] derivedBytes = new byte[targetKeySize * 4];
        int numberOfDerivedWords = 0;
        byte[] block = null;
        MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm);
        while (numberOfDerivedWords < targetKeySize) {
            if (block != null) {
                hasher.update(block);
            }
            hasher.update(password);
            block = hasher.digest(salt);
            hasher.reset();
            for (int i = 1; i < iterations; i++) {
                block = hasher.digest(block);
                hasher.reset();
            }
            System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4, Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4));
            numberOfDerivedWords += block.length / 4;
        }
        System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4);
        System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4);
        return derivedBytes;
    }


    /**
     * AES, CBC, PKCS5Padding, encodeHexString
     * Key 길이가 128, 256 이냐에 따라 알고리즘은 AES-128, AES-256 이 됨
     */
    public static String doDecrypt(String encryptString, String strKey) {
        Assert.notNull(encryptString, "The encryptString must not be null!");
        Assert.notNull(strKey, "The strKey must not be null!");

        if ("".equals(encryptString)) {
            return "";
        }

        SecretKeySpec key = new SecretKeySpec(strKey.getBytes(), "AES");

        try {
            // Hash 변환
            byte[] encryptCombineBytes = Hex.decodeHex(encryptString.toCharArray());

            int encryptSize = encryptCombineBytes.length;
            // 암호화 문자열에 iv 가 포함되어 있지 않을 경우 0.
            int ivSize = 0;
            byte[] ivBytes = new byte[16];
            System.arraycopy(encryptCombineBytes, 0, ivBytes, 0, ivBytes.length);
            encryptSize -= 16;
            // 암호화 문자열에 iv 가 포함되어 있을 경우 IV_SIZE
            ivSize = 16;

            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes);

            // encryptBytes 분리
            byte[] encryptBytes = new byte[encryptSize];
            System.arraycopy(encryptCombineBytes, ivSize, encryptBytes, 0, encryptSize);

            // Decrypt
            Cipher cipherDecrypt = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipherDecrypt.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
            byte[] decryptBytes = cipherDecrypt.doFinal(encryptBytes);

            String decryptResultString = new String(decryptBytes);

            return decryptResultString;
        } catch (Exception ex) {
            throw new RuntimeException("Decrypt Exception!", ex);
        }
    }
}