const CryptoJS = require("crypto-js");

export default class SnsSigCheck {
  /**
   * 生成签名
   * @param {string} method 请求方法 "GET" 或 "POST"
   * @param {string} urlPath URL路径
   * @param {Object} params 请求参数
   * @param {string} secret 密钥
   * @returns {string} Base64编码的签名
   */
  static makeSig(method, urlPath, params, secret) {
    const source = this.makeSource(method, urlPath, params);
    const key = CryptoJS.enc.Utf8.parse(
      secret.replace(/-/g, "+").replace(/_/g, "/")
    );
    const hash = CryptoJS.HmacSHA1(source, key);
    return CryptoJS.enc.Base64.stringify(hash);
  }

  /**
   * 生成签名源字符串
   * @param {string} method 请求方法
   * @param {string} urlPath URL路径
   * @param {Object} params 参数对象
   * @returns {string} 拼接后的字符串
   */
  static makeSource(method, urlPath, params) {
    const sortedParams = this.sortParams(params);
    const paramString = sortedParams
      .map(([key, val]) => `${key}=${val}`)
      .join("&");
    return `${method.toUpperCase()}&${encodeURIComponent(
      urlPath
    )}&${encodeURIComponent(paramString)}`;
  }

  /**
   * 验证签名
   * @param {string} method 请求方法
   * @param {string} urlPath URL路径
   * @param {Object} params 请求参数
   * @param {string} secret 密钥
   * @param {string} sig 待验证的签名
   * @returns {boolean} 验证结果
   */
  static verifySig(method, urlPath, params, secret, sig) {
    delete params.sig;
    const encodedParams = this.encodeValues(params);
    const newSig = this.makeSig(method, urlPath, encodedParams, secret);
    return newSig === sig;
  }

  /**
   * 回调专用参数编码
   * @param {Object} params 参数对象
   * @returns {Object} 编码后的参数对象
   */
  static encodeValues(params) {
    const encoded = {};
    for (const [key, val] of Object.entries(params)) {
      encoded[key] = this.encodeValue(val);
    }
    return encoded;
  }

  /**
   * 专用字符编码
   * @param {string} value 待编码值
   * @returns {string} 编码结果
   */
  static encodeValue(value) {
    return Array.from(value)
      .map((c) => {
        if (/[a-zA-Z0-9!$$*]/.test(c.toString())) return c;
        return (
          "%" + (c as string).charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")
        );
      })
      .join("");
  }

  /**
   * 参数排序方法
   * @param {Object} params 参数对象
   * @returns {Array} 排序后的键值对数组
   */
  static sortParams(params) {
    return Object.keys(params)
      .sort()
      .map((key) => [key, params[key]]);
  }
}

module.exports = SnsSigCheck;
