import * as CryptoJS from 'crypto-js';
// const CryptoJS = require('crypto-js');
import * as NodeRSA from 'node-rsa';
// const NodeRSA = require('node-rsa');

export type Padding = 'Pkcs7' | 'AnsiX923' | 'Iso10126' | 'Iso97971' | 'ZeroPadding' | 'NoPadding';
export type Mode = 'CBC' | 'CFB' | 'CTR' | 'OFB' | 'ECB';

/**
 * crypto工具类, `md5`、`base64`、`sha1`、`sha256`、`hmac`、`aes`、`des`、`rsa`
 * 
 * @see [crypto-js](https://cryptojs.gitbook.io/docs/)
 */
export default class CryptoJsUtils {
    /**
     * `md5`
     */
    static md5(message: string) {
        return CryptoJS.MD5(message).toString();
    }

    /**
     * `sha1`
     */
    static sha1(message: string): string {
        return CryptoJS.SHA1(message).toString();
    }

    /**
     * `sha3`
     */
    static sha3(message: string): string {
        return CryptoJS.SHA3(message).toString();
    }

    /**
     * `sha256`
     */
    static sha256(message: string): string {
        return CryptoJS.SHA256(message).toString();
    }

    /**
     * `sha512`
     */
    static sha512(message: string): string {
        return CryptoJS.SHA512(message).toString();
    }

    /**
     * `hmacMD5`
     * @param message 
     * @param key 
     * @param encoder 
     */
    static hmacMD5(message: string, key: string): string {
        return CryptoJS.HmacMD5(message, key).toString();
    }

    /**
     * `hmacSHA1`
     */
    static hmacSHA1(message: string, key: string): string {
        return CryptoJS.HmacSHA1(message, key).toString();
    }

    /**
     * `hmacSHA256`
     */
    static hmacSHA256(message: string, key: string): string {
        return CryptoJS.HmacSHA256(message, key).toString();
    }

    /**
     * `hmacSHA512`
     */
    static hmacSHA512(message: string, key: string): string {
        return CryptoJS.HmacSHA512(message, key).toString();
    }

    /**
     * `base64`编码
     */
    static base64Encode(data: string) {
        return Buffer.from(data).toString('base64');
    }

    /**
     * `base64`解码
     */
    static base64Decode(data: string) {
        return Buffer.from(data, 'base64').toString();
    }

    /**
     * `aes`加密
     * 
     * ```
     * import { CryptoUtils } from "refs/nslib";
     * const CryptoJS = require('crypto-js');
     * 
     * let encrypted = CryptoJsUtils.aesEncrypt('123456', '123456', { mode: 'CBC' });
     * logger.info(encrypted.toString());
     * 
     * let decrypted = CryptoJsUtils.aesDecrypt(encrypted.toString(), '123456', { mode: 'CBC' });
     * logger.info(decrypted.toString(CryptoJS.enc.Utf8));
     * 
     * //杭停
     * let key = CryptoJS.enc.Hex.parse('88888888');
     * let encrypted = CryptoJsUtils.aesEncrypt('123456', key, { mode: 'ECB' });
     * logger.info(encrypted.toString());
     * 
     * let decrypted = CryptoJsUtils.aesDecrypt(encrypted.ciphertext.toString(CryptoJS.enc.Base64), key, { mode: 'ECB' });
     * logger.info(decrypted.toString(CryptoJS.enc.Utf8));
     * 
     * //iv
     * let iv = CryptoJS.enc.Hex.parse("101112131415161718191a1b1c1d1e1f");
     * let encrypted = CryptoJsUtils.aesEncrypt('123456', '88888888', { mode: 'CBC', iv: iv });
     * logger.info(encrypted.toString());
     * 
     * let decrypted = CryptoJsUtils.aesDecrypt(encrypted.toString(), '88888888', { mode: 'CBC', iv: iv });
     * logger.info(decrypted.toString(CryptoJS.enc.Utf8));
     * ```
     * @param message 需要加密的数据
     * @param key 密钥
     * @param cfg 加解密配置, mode默认:`CBC`, padding默认:`Pkcs7`
     */
    static aesEncrypt(message: any, key: any, cfg?: {
        padding?: Padding,
        mode?: Mode,
        iv?: any
    }) {
        return CryptoJS.AES.encrypt(message, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        });
    }

    /**
     * `aes`解密
     * 
     * @see {@link aesEncrypt}
     */
    static aesDecrypt(ciphertext: any, key: any, cfg?: {
        padding?: Padding,
        mode?: Mode,
        iv?: any
    }) {
        return CryptoJS.AES.decrypt(ciphertext, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        });
    }

    /**
     * `des`加密
     * 
     * ```
     * import { CryptoUtils } from "refs/nslib";
     * const CryptoJS = require('crypto-js');
     * 
     * let encrypted = CryptoJsUtils.desEncrypt('123456', '123456', { mode: 'CBC' });
     * logger.info(encrypted.toString());
     * 
     * let decrypted = CryptoJsUtils.desDecrypt(encrypted.toString(), '123456', { mode: 'CBC' });
     * logger.info(decrypted.toString(CryptoJS.enc.Utf8));
     * ```
     * 
     * @see {@link aesEncrypt}
     * @param message 需要加密的数据
     * @param key 密钥
     * @param cfg 加解密配置, mode默认:`CBC`, padding默认:`Pkcs7`
     */
    static desEncrypt(message: any, key: any, cfg?: {
        padding?: Padding,
        mode?: Mode,
        iv?: any
    }) {
        return CryptoJS.DES.encrypt(message, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        });
    }

    /**
     * `des`解密
     * 
     * @see {@link aesEncrypt}
     */
    static desDecrypt(ciphertext: any, key: any, cfg?: {
        padding?: Padding,
        mode?: Mode,
        iv?: any
    }) {
        return CryptoJS.DES.decrypt(ciphertext, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        });
    }

    /**
     * `3des`/`TripleDES` 加密
     * 
     * ```ts
     * import { CryptoUtils } from "refs/nslib";
     * const CryptoJS = require('crypto-js');
     * 
     * let encrypted = CryptoJsUtils.des3Encrypt('123456', '123456', { mode: 'CBC' });
     * logger.info(encrypted.toString());
     * 
     * let decrypted = CryptoJsUtils.des3Decrypt(encrypted.toString(), '123456', { mode: 'CBC' });
     * logger.info(decrypted.toString(CryptoJS.enc.Utf8));
     * ```
     * @see {@link aesEncrypt}
     * @param message 需要加密的数据
     * @param key 密钥
     * @param cfg 加解密配置, mode默认:`CBC`, padding默认:`Pkcs7`, `iv === string`? `CryptoJS.enc.Hex.parse(iv) : iv` 
     */
    static des3Encrypt(message: any, key: any, cfg?: {
        padding?: Padding,
        mode?: Mode,
        iv?: any
    }) {
        return CryptoJS.TripleDES.encrypt(message, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        });
    }

    /**
     * `3des`/`TripleDES` 解密
     * 
     * @see {@link aesEncrypt}
     */
    static des3Decrypt(ciphertext: any, key: any, cfg?: {
        padding?: Padding,
        mode?: Mode,
        iv?: any
    }) {
        return CryptoJS.TripleDES.decrypt(ciphertext, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        });
    }

    /**
     * 生成`rsa`密钥对
     * 
     * ```
     * import { CryptoUtils } from "refs/nslib";
     * 
     * let keys = CryptoJsUtils.generateRSAKeys();
     * 
     * logger.info(keys.publicKey);
     * logger.info(keys.privateKey);
     * ```
     * 
     * @param publicKeyFormat 公钥格式，默认为：`pkcs8-public-pem`
     * @param privateKeyFormat 私钥格式，默认为：`pkcs8-private-pem`
     */
    static generateRSAKeys(length: number = 1024,
        publicKeyFormat: NodeRSA.FormatPem = 'pkcs8-public-pem',
        privateKeyFormat: NodeRSA.FormatPem = 'pkcs8-private-pem') {
        const key = new NodeRSA({ b: length });
        const publicKey: string = key.exportKey(publicKeyFormat).toString();
        const privateKey: string = key.exportKey(privateKeyFormat).toString();

        return {
            publicKey: publicKey,
            privateKey: privateKey
        };
    }

    /**
     * `rsa`公钥加密
     * 
     * ```
     * import { CryptoUtils } from "refs/nslib";
     * 
     * let keys = CryptoJsUtils.generateRSAKeys();
     * 
     * let encrypted = CryptoJsUtils.rsaEncrypt(keys.publicKey, '123456', { keyFormat: 'pkcs8-public', encryptionScheme: 'pkcs1' });
     * let decrypted = CryptoJsUtils.rsaDecrypt(keys.privateKey, encrypted, { keyFormat: 'pkcs8-private', encryptionScheme: 'pkcs1' });
     * ```
     * @param publicKey 公钥
     * @param data 
     * @param cfg 公钥格式 keyFormat 默认:`pkcs8-public`, 加密方案 encryptionScheme 默认:`pkcs1`
     */
    static rsaEncrypt(publicKey: NodeRSA.Key, data: NodeRSA.Data, cfg?: {
        /**
         * 公钥格式
         */
        keyFormat?: NodeRSA.Format,
        /**
         * 加密方案
         */
        encryptionScheme?: NodeRSA.EncryptionScheme,
    }) {
        const pub = new NodeRSA(publicKey, cfg?.keyFormat || 'pkcs8-public');
        pub.setOptions({ encryptionScheme: cfg?.encryptionScheme || 'pkcs1' });
        return pub.encrypt(data, 'base64');
    }

    /**
     * `rsa`私钥解密
     * 
     * @param privateKey 私钥
     * @param data 
     * @param cfg 私钥格式 keyFormat 默认:`pkcs8-private`, 加密方案 encryptionScheme 默认:`pkcs1`
     */
    static rsaDecrypt(privateKey: NodeRSA.Key, data: Buffer | string, cfg?: {
        /**
         * 私钥格式
         */
        keyFormat?: NodeRSA.Format,
        /**
         * 加密方案
         */
        encryptionScheme?: NodeRSA.EncryptionScheme,
    }) {
        const pri = new NodeRSA(privateKey, cfg?.keyFormat || 'pkcs8-private');
        pri.setOptions({ encryptionScheme: cfg?.encryptionScheme || 'pkcs1' });
        return pri.decrypt(data, 'utf8');
    }

    /**
     * `rsa`私钥签名
     * ```
     * import { CryptoUtils } from "refs/nslib";
     * 
     * let keys = CryptoJsUtils.generateRSAKeys();
     * 
     * let sign = CryptoJsUtils.rsaSign(keys.privateKey, '123456', { keyFormat: 'pkcs8-private', signingScheme: 'pkcs1-sha1' });
     * let verify = CryptoJsUtils.rsaVerifySign(keys.publicKey, '123456', sign, { keyFormat: 'pkcs8-public', signingScheme: 'pkcs1-sha1' });
     * 
     * ```
     * @param cfg 私钥格式 keyFormat 默认：`pkcs8-private`, 签名方案 signingScheme 默认：`pkcs1-sha1`
     */
    static rsaSign(privateKey: NodeRSA.Key, data: NodeRSA.Data, cfg?: {
        /**
         * 公钥格式
         */
        keyFormat?: NodeRSA.Format,
        /**
         * 签名方案
         */
        signingScheme?: NodeRSA.SigningScheme | NodeRSA.SigningSchemeHash | NodeRSA.AdvancedSigningScheme | undefined
    }) {
        const pri = new NodeRSA(privateKey, cfg?.keyFormat || 'pkcs8-private');
        pri.setOptions({ signingScheme: cfg?.signingScheme || 'pkcs1-sha1' });
        return pri.sign(data, 'base64');
    }

    /**
     * `rsa`公钥验签
     * 
     * @param cfg 公钥格式 keyFormat 默认：`pkcs8-public`, 签名方案 signingScheme 默认：`pkcs1-sha1`
     * @see {@link rsaSign}
     */
    static rsaVerifySign(publicKey: NodeRSA.Key, data: NodeRSA.Data, sign: string, cfg?: {
        /**
         * 公钥格式
         */
        keyFormat?: NodeRSA.Format,
        /**
         * 签名方案
         */
        signingScheme?: NodeRSA.SigningScheme | NodeRSA.SigningSchemeHash | NodeRSA.AdvancedSigningScheme | undefined
    }) {
        const pub = new NodeRSA(publicKey, cfg?.keyFormat || 'pkcs8-public');
        pub.setOptions({ signingScheme: cfg?.signingScheme || 'pkcs1-sha1' });
        return pub.verify(data, Buffer.from(sign, 'base64'));
    }
}

export { CryptoJsUtils }