/**
 * 加解密、编码模块
 */

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

/**
 * CryptoJS.pad
 */
type Padding = 'Pkcs7' | 'AnsiX923' | 'Iso10126' | 'Iso97971' | 'ZeroPadding' | 'NoPadding';

/**
 * CryptoJS.mode
 */
type Mode = 'CBC' | 'CFB' | 'CTR' | 'OFB' | 'ECB';

/**
 * `crypto-js`加解密, `md5`、`sha1`、`sha256`、`hmac`、`aes`、`des`
 * 
 * @see [crypto-js](https://cryptojs.gitbook.io/docs/)
 */
export class CryptoUtils {
    /**
     * `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();
    }

    /**
     * `aes`加密
     * 
     * ```ts
     * import { CryptoUtils } from "refs/nslib";
     * const CryptoJS = require('crypto-js');
     * 
     * let encrypted = CryptoJsUtils.aesEncrypt('123456', '123456', { mode: 'CBC' });
     * let decrypted = CryptoJsUtils.aesDecrypt(encrypted.toString(), '123456', { mode: 'CBC' });
     * 
     * //杭停
     * let key = CryptoJS.SHA1(CryptoJS.SHA1('01ac8d88a37ce1f1d3673db8f3f6a379')).toString().substring(0, 32);
     * let encrypted = CryptoUtils.aesEncrypt(JSON.stringify({ code: 1, message: 'info' }), key, { mode: 'ECB' });
     * let decrypted = CryptoUtils.aesDecrypt(encrypted, key, { mode: 'ECB' });
     * 
     * //iv
     * let iv = CryptoJS.enc.Hex.parse("101112131415161718191a1b1c1d1e1f");
     * let encrypted = CryptoJsUtils.aesEncrypt('123456', '88888888', { iv: iv });
     * let decrypted = CryptoJsUtils.aesDecrypt(encrypted, '88888888', { iv: iv });
     * ```
     * @param message 需要加密的数据
     * @param key 密钥
     * @param cfg 加解密配置
     */
    static aesEncrypt(message: any, key: any, cfg?: {
        /**
         * 默认`Pkcs7`
         */
        padding?: Padding,
        /**
         * 默认`CBC`
         */
        mode?: Mode,
        /**
         * iv 不能直接传递字符串, `CryptoJS.enc.Hex.parse(iv)`;
         */
        iv?: any
    }) {
        return CryptoJS.AES.encrypt(message, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        }).toString();
    }

    /**
     * `aes`解密
     * 
     * ```ts
     * import { CryptoUtils } from "refs/nslib";
     * const CryptoJS = require('crypto-js');
     * 
     * let encrypted = CryptoJsUtils.aesEncrypt('123456', '123456', { mode: 'CBC' });
     * let decrypted = CryptoJsUtils.aesDecrypt(encrypted.toString(), '123456', { mode: 'CBC' });
     * 
     * //杭停
     * let key = CryptoJS.SHA1(CryptoJS.SHA1('01ac8d88a37ce1f1d3673db8f3f6a379')).toString().substring(0, 32);
     * let encrypted = CryptoUtils.aesEncrypt(JSON.stringify({ code: 1, message: 'info' }), key, { mode: 'ECB' });
     * let decrypted = CryptoUtils.aesDecrypt(encrypted, key, { mode: 'ECB' });
     * 
     * //iv
     * let iv = CryptoJS.enc.Hex.parse("101112131415161718191a1b1c1d1e1f");
     * let encrypted = CryptoJsUtils.aesEncrypt('123456', '88888888', { iv: iv });
     * let decrypted = CryptoJsUtils.aesDecrypt(encrypted, '88888888', { iv: iv });
     * ```
     * @param ciphertext 需要解密的数据
     * @param key 密钥
     * @param cfg 加解密配置
     */
    static aesDecrypt(ciphertext: any, key: any, cfg?: {
        /**
         * 默认`Pkcs7`
         */
        padding?: Padding,
        /**
         * 默认`CBC`
         */
        mode?: Mode,
        /**
         * iv 不能直接传递字符串, `CryptoJS.enc.Hex.parse(iv)`;
         */
        iv?: any
    }) {
        return CryptoJS.AES.decrypt(ciphertext, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        }).toString(CryptoJS.enc.Utf8);
    }

    /**
     * `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));
     * ```
     * 
     * @param message 需要加密的数据
     * @param key 密钥
     * @param cfg 加解密配置
     */
    static desEncrypt(message: any, key: any, cfg?: {
        /**
          * 默认`Pkcs7`
          */
        padding?: Padding,
        /**
         * 默认`CBC`
         */
        mode?: Mode,
        /**
         * iv 不能直接传递字符串, `CryptoJS.enc.Hex.parse(iv)`;
         */
        iv?: any
    }) {
        return CryptoJS.DES.encrypt(message, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        }).toString();
    }

    /**
     * `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));
     * ```
     * 
     * @param ciphertext 需要解密的数据
     * @param key 密钥
     * @param cfg 加解密配置
     */
    static desDecrypt(ciphertext: any, key: any, cfg?: {
        /**
         * 默认`Pkcs7`
         */
        padding?: Padding,
        /**
         * 默认`CBC`
         */
        mode?: Mode,
        /**
         * iv 不能直接传递字符串, `CryptoJS.enc.Hex.parse(iv)`;
         */
        iv?: any
    }) {
        return CryptoJS.DES.decrypt(ciphertext, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        }).toString(CryptoJS.enc.Utf8);
    }

    /**
     * `3des`/`TripleDES` 加密
     * 
     * ```ts
     * import { CryptoUtils } from "refs/nslib";
     * const CryptoJS = require('crypto-js');
     * 
     * //非标加解密
     * let key = CryptoJS.enc.Utf8.parse('111111111111111111111111');
     * let iv = CryptoJS.enc.Utf8.parse('11111111');
     * let encrypted = CryptoJsUtils.des3Encrypt(JSON.stringify({ code: 1 }), key, { iv: iv });
     * let decrypted = CryptoJsUtils.des3Decrypt(encrypted, key, { iv: iv });
     * ```
     * @param message 需要加密的数据
     * @param key 密钥
     * @param cfg 加解密配置
     */
    static des3Encrypt(message: any, key: any, cfg?: {
        /**
         * 默认`Pkcs7`
         */
        padding?: Padding,
        /**
         * 默认`CBC`
         */
        mode?: Mode,
        /**
         * iv 不能直接传递字符串, `CryptoJS.enc.Hex.parse(iv)`;
         */
        iv?: any
    }) {
        return CryptoJS.TripleDES.encrypt(message, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        }).toString();
    }

    /**
     * `3des`/`TripleDES` 解密
     * 
     * ```ts
     * import { CryptoUtils } from "refs/nslib";
     * const CryptoJS = require('crypto-js');
     * 
     * //非标加解密
     * let key = CryptoJS.enc.Utf8.parse('111111111111111111111111');
     * let iv = CryptoJS.enc.Utf8.parse('11111111');
     * let encrypted = CryptoJsUtils.des3Encrypt(JSON.stringify({ code: 1 }), key, { iv: iv });
     * let decrypted = CryptoJsUtils.des3Decrypt(encrypted, key, { iv: iv });
     * ```
     * @param ciphertext 需要解密的数据
     * @param key 密钥
     * @param cfg 加解密配置
     */
    static des3Decrypt(ciphertext: any, key: any, cfg?: {
        /**
          * 默认`Pkcs7`
          */
        padding?: Padding,
        /**
         * 默认`CBC`
         */
        mode?: Mode,
        /**
         * iv 不能直接传递字符串, `CryptoJS.enc.Hex.parse(iv)`;
         */
        iv?: any
    }) {
        return CryptoJS.TripleDES.decrypt(ciphertext, key, {
            padding: CryptoJS.pad[cfg?.padding || 'Pkcs7'],
            mode: CryptoJS.mode[cfg?.mode || 'CBC'],
            iv: cfg?.iv
        }).toString(CryptoJS.enc.Utf8);
    }
}

/**
 * `node-rsa` rsa加解密、签名、延签工具
 */
export class RSAUtils {
    /**
     * 生成`rsa`密钥对
     * 
     * ```
     * import { RSAUtils } from "refs/nslib";
     * 
     * let keys = RSAUtils.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 });

        return {
            publicKey: key.exportKey(publicKeyFormat).toString(),
            privateKey: key.exportKey(privateKeyFormat).toString()
        };
    }

    /**
     * `rsa`公钥加密
     * 
     * ```
     * import { RSAUtils } from "refs/nslib";
     * 
     * let keys = RSAUtils.generateRSAKeys();
     * let encrypted = RSAUtils.rsaEncrypt('123456', keys.publicKey);
     * let decrypted = RSAUtils.rsaDecrypt(encrypted, keys.privateKey);
     * ```
     * @param key 公钥
     * @param format 公钥格式 默认 `pkcs8-public`
     * @param options 默认 `{ encryptionScheme: 'pkcs1' }` 常见：pkcs1-sha1、pkcs1-md5、pkcs1_oaep
     */
    static rsaEncrypt(data: NodeRSA.Data, key: NodeRSA.Key, format: NodeRSA.Format = 'pkcs8-public', options?: NodeRSA.Options) {
        const pub = new NodeRSA(key, format, Object.assign({ encryptionScheme: 'pkcs1' }, options));
        return pub.encrypt(data, 'base64');
    }

    /**
     * `rsa`私钥解密
     * 
     * ```
     * import { RSAUtils } from "refs/nslib";
     * 
     * let keys = RSAUtils.generateRSAKeys();
     * let encrypted = RSAUtils.rsaEncrypt('123456', keys.publicKey);
     * let decrypted = RSAUtils.rsaDecrypt(encrypted, keys.privateKey);
     * ```
     * @param key 私钥
     * @param format 私钥格式 默认 `pkcs8-private`
     * @param options 默认 `{ encryptionScheme: 'pkcs1' }` 常见：pkcs1-sha1、pkcs1-md5、pkcs1_oaep
     */
    static rsaDecrypt(data: Buffer | string, key: NodeRSA.Key, format: NodeRSA.Format = 'pkcs8-private', options?: NodeRSA.Options) {
        const pri = new NodeRSA(key, format, Object.assign({ encryptionScheme: 'pkcs1' }, options));
        return pri.decrypt(data, 'utf8');
    }

    /**
     * `rsa`私钥签名
     * 
     * ```
     * import { RSAUtils } from "refs/nslib";
     * 
     * let keys = RSAUtils.generateRSAKeys();
     * let sign = RSAUtils.rsaSign('123456', keys.privateKey);
     * let verify = RSAUtils.rsaVerifySign('123456', sign, keys.publicKey);
     * ```
     * @param key 私钥
     * @param format 私钥格式 默认 `pkcs8-private`
     * @param options 默认 `{ signingScheme: 'pkcs1' }` 常见：pkcs1-sha1、pkcs1-md5、pkcs1_oaep
     */
    static rsaSign(data: NodeRSA.Data, key: NodeRSA.Key, format: NodeRSA.Format = 'pkcs8-private', options?: NodeRSA.Options) {
        const pri = new NodeRSA(key, format, Object.assign({ signingScheme: 'pkcs1' }, options));
        return pri.sign(data, 'base64');
    }

    /**
     * `rsa`公钥验签
     * 
     * ```
     * import { RSAUtils } from "refs/nslib";
     * 
     * let keys = RSAUtils.generateRSAKeys();
     * let sign = RSAUtils.rsaSign('123456', keys.privateKey);
     * let verify = RSAUtils.rsaVerifySign('123456', sign, keys.publicKey);
     * ```
     * @param key 公钥
     * @param format 公钥格式 默认 `pkcs8-private`
     * @param options 默认 `{ signingScheme: 'pkcs1' }`
     */
    static rsaVerifySign(data: NodeRSA.Data, sign: string, publicKey: NodeRSA.Key, format: NodeRSA.Format = 'pkcs8-public', options?: NodeRSA.Options) {
        const pub = new NodeRSA(publicKey, format, Object.assign({ signingScheme: 'pkcs1' }, options));
        return pub.verify(data, Buffer.from(sign, 'base64'));
    }
}

/**
 * `base64`工具类
 */
export class Base64Utils {
    /**
     * `base64`编码
     */
    static encode(str: string) {
        return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(str));
    }

    /**
     * `base64`解码
     */
    static decode(str: string) {
        return CryptoJS.enc.Base64.parse(str).toString(CryptoJS.enc.Utf8);
    }

    /**
     * 获取base64文件大小(单位:kb)
     */
    static getFileSize(base64: string) {
        return (4 * Math.ceil((base64.length / 3)) * 0.5624896334383812) / 1000;
    }
}

/**
 * 国密2帮助类
 * 
 * 基于[ sm-crypto ](https://www.npmjs.com/package/sm-crypto)开发
 * 
 * Asn1 在线解析
 * 
 * https://the-x.cn/encodings/Asn1.aspx
 * 
 * http://lapo.it/asn1js/#
 */
export class Sm2Util {
    /**
     * 签名
     * 
     * 注：
     * 1.传入公钥的话，可以去掉sm3杂凑中推导公钥的过程，速度会比纯签名 + 生成椭圆曲线点 + sm3杂凑快
     * 
     * 2.如果没有公钥，传入 pointPool: [sm2.getPoint(), sm2.getPoint(), sm2.getPoint(), sm2.getPoint()],稍微快点
     * @param data 
     * @param privateKey 私钥，需传最终参与签名的16进制字符串，例：fd89d3ebd38097f5a5350c7df9033bfc75d911b7a17254d49d5ca2b4ba2dbcf1
     * @param opts pointPool: [sm2.getPoint(), sm2.getPoint(), sm2.getPoint(), sm2.getPoint()],
     * @returns 
     */
    static sign(data: string, privateKey: string, opts?: { pointPool?:any, der?: boolean, hash?: boolean, publicKey?:string }) {
        const { sm2 } = require('sm-crypto');

        let sign = sm2.doSignature(data, privateKey, {
            pointPool: opts?.pointPool,
            der: opts?.der,
            hash: opts?.hash,
            publicKey: opts?.publicKey
        });

        return Buffer.from(sign, 'hex').toString('base64');
    }

    /**
     * 验签
     * 
     * @param sign 签名结果(base64)
     * @param publicKey 公钥，需传最终参与验签的16进制字符串，例：047d4398d82a2565021d682b6fd7b1d9c0f194a8ceddeb3ad1bcb8b45cd4c9f377872b748dd29d6a3b2f6081a280cf931076bc8908ac2259512b015660e6c6fab8
     */
    static verifySign(data: string, sign: string, publicKey: string, opt?: { der?: boolean, hash?: boolean }) {
        const { sm2 } = require('sm-crypto');
        
        return sm2.doVerifySignature(data,
            Buffer.from(sign, 'base64').toString('hex'),
            publicKey, {
            der: opt?.der,
            hash: opt?.hash,
        });
    }

    /**
     * 加密
     * 
     * @param publicKey 公钥，需传最终参与加密的16进制字符串，例：047d4398d82a2565021d682b6fd7b1d9c0f194a8ceddeb3ad1bcb8b45cd4c9f377872b748dd29d6a3b2f6081a280cf931076bc8908ac2259512b015660e6c6fab8
     * @param cipherMode 0 - C1C2C3,1 - C1C3C2，默认为1
     */
    static encrypt(data: string, publicKey: string, cipherMode?: 0 | 1) {
        const { sm2 } = require('sm-crypto');

        let encrypted = sm2.doEncrypt(data, publicKey, cipherMode);
        //给JAVA解密需加04
        return Buffer.from('04' + encrypted, 'hex').toString('base64');
    }

    /**
     * 解密
     * 
     * @param data 待解密的字符串(base64)
     * @param privateKey 私钥，需传最终参与签名的16进制字符串，例：fd89d3ebd38097f5a5350c7df9033bfc75d911b7a17254d49d5ca2b4ba2dbcf1
     * @param cipherMode 0 - C1C2C3,1 - C1C3C2，默认为1
     */
    static decrypt(data: string, privateKey:string, cipherMode?: 0 | 1) {
        const { sm2 } = require('sm-crypto');
        
        //JAVA密文需要去04再解密
        let decrypted = sm2.doDecrypt(Buffer.from(data, 'base64').toString('hex').substring(2), privateKey, cipherMode);
        return decrypted;
    }
}