import CryptoJS from 'crypto-js';

/** AES密钥 */
const aeskey: CryptoJS.lib.WordArray = CryptoJS.enc.Utf8.parse('F7A72DE7D6264530F01BA49BC73EB873');
/** MAC检验码左8字节 */
const macKeyLeft: CryptoJS.lib.WordArray = CryptoJS.enc.Hex.parse('B0FB83E39A5EBFAA'); // 这里要用CryptoJS.enc.Hex.parse，CryptoJS.enc.Utf8.parse返回是16位的
/** MAC检验码右8字节 */
const macKeyRight: CryptoJS.lib.WordArray = CryptoJS.enc.Hex.parse('BE471362A58393FF');
/** Phone AES密钥 */
const phoneaeskey = '0d299ce2d4105282f7471074cb0f9f9d';
/** MAC检验向量 */
const iv = new Uint8Array([0xd9, 0x51, 0xdb, 0xe0, 0x37, 0xc8, 0x23, 0x25]);

const aesOption = {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7,
};
const desOption = {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.NoPadding,
};

/**
 * 将Uint8Array数据转化为WordArray数据的方法
 * @param arr Uint8Array数据
 */
function uint8ArrayToWordArray(arr: Uint8Array): CryptoJS.lib.WordArray {
    const word = [];
    for (let i = 0; i < arr.length; i += 4) {
        word.push((arr[i] << 24) | (arr[i + 1] << 16) | (arr[i + 2] << 8) | (arr[i + 3] << 0));
    }
    macKeyLeft;

    return CryptoJS.lib.WordArray.create(word, arr.length);
}

/**
 * 将WordArray数据转化为Uint8Array数据的方法
 * @param wordArray WordArray数据
 */
export function wordArrayToUint8Array(wordArray: CryptoJS.lib.WordArray): Uint8Array {
    const arrayOfWords = Object.prototype.hasOwnProperty.call(wordArray, 'words') ? wordArray.words : [];
    const length = Object.prototype.hasOwnProperty.call(wordArray, 'sigBytes') ? wordArray.sigBytes : arrayOfWords.length * 4;
    const uInt8Array = new Uint8Array(length);
    let index = 0;
    let word;
    for (let i = 0; i < length; i++) {
        word = arrayOfWords[i];
        uInt8Array[index++] = word >> 24;
        uInt8Array[index++] = (word >> 16) & 0xff;
        uInt8Array[index++] = (word >> 8) & 0xff;
        uInt8Array[index++] = word & 0xff;
    }
    return uInt8Array;
}

/**
 * MAC校检码生成子方法
 * @param plainText 目标数据
 * @param key DES密钥
 */
function dataMacAnsiX99(plainText: Uint8Array, key: CryptoJS.lib.WordArray): Uint8Array | null {
    if (plainText.length % 8 !== 0) {
        return null;
    }

    const macData = new Uint8Array(8);
    macData.set(iv, 0);

    const xorData = new Uint8Array(8);
    for (let i = 0; i < plainText.length; i += 8) {
        for (let j = 0; j < 8; j++) {
            xorData[j] = macData[j] ^ plainText[i + j];
        }

        const desData = CryptoJS.DES.encrypt(uint8ArrayToWordArray(xorData), key, desOption);
        const desBase64 = CryptoJS.enc.Base64.parse(desData.toString());
        const desArray = wordArrayToUint8Array(desBase64);
        for (let k = 0; k < desArray.length; k++) {
            macData[k] = desArray[k];
        }
    }

    return macData;
}

/**
 * MAC校检码生成方法
 * @param plainText 目标数据
 */
function dataMacAnsiX919(plainText: Uint8Array): Uint8Array | null {
    const data1 = dataMacAnsiX99(plainText, macKeyLeft);
    if (data1 === null) {
        return null;
    }

    const cipherParams = CryptoJS.lib.CipherParams.create({
        ciphertext: uint8ArrayToWordArray(data1),
    });
    const data2 = CryptoJS.DES.decrypt(cipherParams, macKeyRight, desOption);
    const retData = CryptoJS.DES.encrypt(data2, macKeyLeft, desOption);
    const retBase64 = CryptoJS.enc.Base64.parse(retData.toString());

    return wordArrayToUint8Array(retBase64);
}

/**
 * 十六进制字符串转Uint8Array
 * @param str 
 * @returns 
 */
function hexStringToUint8Array(str: string) {
    const buffer = str.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16));
    return new Uint8Array(buffer ?? []);
}

/**
 * 5.0报文数据加密方法
 * @param plainText 明文
 */
export const encrypt50 = (plainText: Uint8Array) => {
    if (plainText === null) {
        return null;
    }

    const a = CryptoJS.AES.encrypt(uint8ArrayToWordArray(plainText), aeskey, aesOption);
    const a1 = CryptoJS.enc.Base64.parse(a.toString());
    const a2 = wordArrayToUint8Array(a1);

    const data1 = new Uint8Array(4 + a2.length + 8);
    const dataView = new DataView(new ArrayBuffer(4));
    dataView.setUint32(0, plainText.length, true);
    const lengthArray = new Uint8Array(dataView.buffer);

    data1.set(lengthArray);
    data1.set(a2, 4);

    const mDataTemp = new Uint8Array(8);
    mDataTemp.set(lengthArray);
    mDataTemp.set(new Uint8Array(4), 4);
    const macData = dataMacAnsiX919(mDataTemp);
    if (macData === null) {
        return null;
    }
    data1.set(macData, 4 + a2.length);

    return data1;
};

/**
 * 5.0报文数据解密方法
 * @param encryptData 
 * @returns 
 */
export const decrypt50 = (encryptData: Uint8Array) => {
    encryptData = encryptData.subarray(4, encryptData.length - 8);
    const cipherParams = CryptoJS.lib.CipherParams.create({
        ciphertext: uint8ArrayToWordArray(encryptData),
    });
    const decrytped = CryptoJS.AES.decrypt(cipherParams, aeskey, aesOption);
    return wordArrayToUint8Array(decrytped);
};

/**
 * 5.0报文数据加密方法
 * @param data 
 * @returns 
 */
export const encryptBody = (data: string) => {
    const text = new TextEncoder()
    const content = text.encode(data)
    const encryptData = encrypt50(content)
    if (encryptData) {
        const words = uint8ArrayToWordArray(encryptData)
        return CryptoJS.enc.Base64.stringify(words)
    }
    return ''
}

/**
 * 5.0报文数据解密方法
 * @param base64 
 * @returns 
 */
export const decryptBody = (base64: string) => {
    const words = CryptoJS.enc.Base64.parse(base64) // 解析base64
    const content = wordArrayToUint8Array(words)
    const decryptData = decrypt50(content)
    return new TextDecoder().decode(decryptData)
}

/**
 * AES数据解密方法
 * @param encryptData 密文
 * @param size 明文长度
 */
export const decryptAES = (value: string) => {
    try {
        const ciphertext = hexStringToUint8Array(value);
        const key = hexStringToUint8Array(phoneaeskey);

        const cipherParams = CryptoJS.lib.CipherParams.create({
            ciphertext: uint8ArrayToWordArray(ciphertext),
        });

        const decrytped = CryptoJS.AES.decrypt(cipherParams, uint8ArrayToWordArray(key), aesOption);
        const h = wordArrayToUint8Array(decrytped);
        return new TextDecoder().decode(h);
    } catch {
        return ''
    }
}