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');
/** 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数据
 */
function wordArrayToUint8Array(wordArray: CryptoJS.lib.WordArray): Uint8Array {
    const words = wordArray.words;
    const byteArray = [];
    for (let i = 0; i < words.length; ++i) {
        const word = words[i];
        for (let j = 3; j >= 0; --j) {
            byteArray.push((word >> (8 * j)) & 0xff);
        }
    }

    return new Uint8Array(byteArray);
}

/**
 * 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);
}

/**
 * 5.0报文数据加密方法
 * @param plainText 明文
 */
export const encrypt50 = (plainText: Uint8Array): Uint8Array | null => {
    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 密文
 * @param size 明文长度
 */
export const decrypt50 = (encryptData: Uint8Array, size: number): Uint8Array | null => {
    const cipherParams = CryptoJS.lib.CipherParams.create({
        ciphertext: uint8ArrayToWordArray(encryptData),
    });
    const decrytped = CryptoJS.AES.decrypt(cipherParams, aeskey, aesOption);

    const a = wordArrayToUint8Array(decrytped);
    if (a.length < size) {
        return null;
    }

    return a.subarray(0, size);
};
