<?php

namespace app\service\util;

use app\exception\LogicException;
use app\util\sm\cmb\Sm;
use app\util\StringUtil;

class CmbLifeUtils
{
    protected static string $cmbLifeProtocolPrefix = 'cmblife://';

    /**
     * 生成掌上生活协议,带有签名
     * @param string $funcName
     * @param array $params
     * @return string
     * @throws LogicException
     */
    public static function genProtocol(string $funcName, array $params): string
    {
        if (empty($funcName)) {
            throw new LogicException('funcName不能为空');
        }
        // 处理公共参数
        $cmbConfig = config('cmb');
        $params['mid'] = $cmbConfig['mid'];
        $params['aid'] = $cmbConfig['aid'];
        $params['date'] = date('YmdHis');
        $params['random'] = StringUtil::generateRandomString(32);// 随机数
        $params['keyAlias'] = 'CO_PUB_KEY_SM2';
        $params['cmbKeyAlias'] = 'SM2_CMBLIFE';
        $signBody = self::assembleProtocol($funcName, $params, false); // 不需要urlEncode
        $signKey = $cmbConfig['merchant_sm2_pri_key'];
        $sign = Sm::sign($signKey, $signBody);  //sm2
        $params['sign'] = $sign;
        return self::assembleProtocol($funcName, $params, true);
    }

    /***
     * 拼接掌上生活协议
     * @param String $funcName
     * @param array $params
     * @param bool $isUrlEncode
     * @return string
     */
    public static function assembleProtocol(string $funcName, array $params, bool $isUrlEncode)
    {
        $prefix = self::$cmbLifeProtocolPrefix . $funcName;
        return self::getAssembleUrl($prefix, $params, $isUrlEncode);
    }

    /**
     * 拼接签名字符串
     * @param String $prefix
     * @param String $queryString
     * @return String
     */
    public static function assembleUrl(string $prefix, string $queryString): string
    {
        if (empty($prefix)) {
            return $queryString;
        } else {
            $char = strpos($prefix, '?') !== false ? '&' : '?';
            return $prefix . $char . $queryString;
        }
    }

    /**
     * 拼接签名字符串
     * @param string $prefix
     * @param array $params
     * @param bool $isUrlEncode
     * @return String
     */
    public static function getAssembleUrl(string $prefix, array $params, bool $isUrlEncode): string
    {
        return self::assembleUrl($prefix, self::mapToQueryString($params, true, $isUrlEncode));
    }


    /**
     * 参数转为queryString
     * @param array $params
     * @param bool $isSort 是否排序
     * @param bool $isUrlEncode 是否需要urlEncode
     * @return string
     */
    public static function mapToQueryString(array $params, bool $isSort, bool $isUrlEncode = true): string
    {
        if ($isSort) {
            ksort($params);
        }
        $queryString = '';
        foreach ($params as $key => $value) {
            if (empty($value)) {
                continue;
            }
            if ($isUrlEncode) {
                $value = urlencode($value);
            }
            $queryString .= $key . '=' . $value . '&';
        }
        return trim($queryString, '&');
    }

    /**
     * 加密
     * @param string $encryptBody
     * @param string $encryptKey 加密使用的key SM2公钥
     * @return string
     * @throws LogicException
     */
    public static function encrypt(string $encryptBody, string $encryptKey)
    {
        if (empty($encryptBody)) {
            throw new LogicException('报文不能为空');
        }
        if (empty($encryptKey)) {
            throw new LogicException('公钥不能为空');
        }
        return Sm::sm4Encrypt($encryptKey, $encryptBody);
    }

    /**
     * 解密
     * @param string $encryptBody
     * @param string $decryptKey 解密使用的Key SM2私钥
     * @throws LogicException
     */
    public static function decrypt(string $encryptBody, string $decryptKey)
    {
        if (empty($encryptBody)) {
            throw new LogicException('报文不能为空!');
        }
        if (empty($decryptKey)) {
            throw new LogicException('秘钥不能为空!');
        }
        return json_decode(Sm::sm4decrypt($decryptKey, $encryptBody), true);
    }

    /**
     * 签名
     * @param string $signBody
     * @param string $signKey
     * @param string $algorithmEnum
     * @return string
     * @throws LogicException
     */
    public static function sign(string $signBody, string $signKey)
    {
        if (empty($signBody)) {
            throw new LogicException('待签名数据不能为空!');
        }
        if (empty($signKey)) {
            throw new LogicException('私钥不能为空');
        }
        return Sm::sign($signKey, $signBody);

    }

    /**
     * 验签,国密算法SM3WithSM2
     * @param string $verifyBody
     * @param string $sign
     * @param string $verifyKey  sm2公钥
     * @return bool
     * @throws LogicException
     */
    public static function verify(string $verifyBody, string $sign, string $verifyKey)
    {
        if (empty($verifyBody)) {
            throw new LogicException('验签数据不能为空');
        }
        if (empty($sign)) {
            throw new LogicException("签名不能为空");
        }
        if (empty($verifyKey)) {
            throw new LogicException("公钥不能为空");
        }
        return Sm::verify($verifyKey, $sign, $verifyBody);
    }

    /**
     * 对请求签名
     * @param string $funcName
     * @param array $params
     * @param string $signKey sm2 私钥
     * @param string $signAlgorithm SM3WithSM2
     * @return string
     */
    public static function signForRequest(string $funcName, array $params, string $signKey, string $signAlgorithm = 'SM3WithSM2')
    {
        if (!empty($funcName) && !strpos($funcName, '.json')) {
            $funcName = $funcName . '.json';
        }
        $assembleUrl = self::getAssembleUrl($funcName, $params, false);
        return self::sign($assembleUrl, $signKey);
    }

    /**
     * 对响应签名
     * @param array $params
     * @return string
     * @throws LogicException
     */
    public static function signForResponse(array $params)
    {
        if (empty($params)) {
            throw new LogicException('参数不能为空');
        }
        $cmbConfig = config('cmb');
        $signKey = $cmbConfig['merchant_sm2_pri_key'];
        $assembleUrl = self::getAssembleUrl('', $params, false);
        unset($params['sign']);
        return self::sign($assembleUrl, $signKey);
    }
    /**
     * 对响应验签
     * @param array $data
     * @return bool true 验签成功 false 验签失败
     * @throws LogicException
     */
    public static function verifyForResponse(array $data): bool
    {
        if (empty($data)) {
            throw new LogicException('参数不能为空');
        }
        $cmbConfig = config('cmb');
        $verifyKey = $cmbConfig['merchant_sm2_pub_key'];
        $sign = $data['sign'];
        unset($data['sign']);
        return self::verify(self::getAssembleUrl('', $data, false), $sign, $verifyKey);
    }

    /**
     * @param array $params
     * @param string $verifyKey 验签所使用的Key,为掌上生活公钥
     * @return bool
     * @throws LogicException
     */
    public  static function verifyForRequest(array $params, string $verifyKey): bool
    {
        if (empty($params)) {
            throw new LogicException('参数不能为空');
        }
        $sign = $params['sign'];
        unset($params['sign']);
        $assembleUrl = self::getAssembleUrl('', $params, false);
        return self::verify($assembleUrl, $sign, $verifyKey);
    }

}