<?php

namespace App\Services;

use Exception;

class PayUtils
{
    /**
     * 验证签名
     *
     * @param array $params 参数数组（包含 sign 和 signType）
     * @param string $key 密钥
     * @return bool
     */
    public static function verifySign(array $params, string $key): bool
    {
        if (!isset($params['sign']) || empty(trim($params['sign']))) {
            throw new Exception("Missing 'sign' field in parameters.");
        }

        if (!isset($params['signType']) || empty(trim($params['signType']))) {
            throw new Exception("Missing 'signType' field in parameters.");
        }

        $sign = strtoupper($params['sign']);
        $signType = strtoupper($params['signType']);

        // 复制参数并移除 sign 字段
        $paramsCopy = $params;
        unset($paramsCopy['sign']);

        $expectedSign = self::getSign(self::getSignStr($paramsCopy, $key), $signType);
        return $sign === $expectedSign;
    }

    /**
     * 对参数进行签名
     *
     * @param array $params 原始参数
     * @param string $key 密钥
     * @return array 已签名的参数数组
     */
    public static function sign(array $params, string $key): array
    {
        if (!isset($params['signType']) || empty(trim($params['signType']))) {
            throw new Exception("Missing 'signType' field in parameters.");
        }

        $signType = strtoupper($params['signType']);
        $signStr = self::getSignStr($params, $key);
        $signValue = self::getSign($signStr, $signType);

        $params['sign'] = $signValue;
        return $params;
    }

    /**
     * 根据签名类型计算签名值
     *
     * @param string $signStr 待签名字符串
     * @param string $signType 签名类型 (MD5/SHA1/SHA256)
     * @return string
     */
    public static function getSign(string $signStr, string $signType): string
    {
        switch ($signType) {
            case 'MD5':
                return strtoupper(md5($signStr));
            case 'SHA1':
                return strtoupper(sha1($signStr));
            case 'SHA256':
                return strtoupper(hash('sha256', $signStr));
            default:
                throw new Exception("Unsupported signature type: " . $signType);
        }
    }

    /**
     * 构建待签名字符串（排除 sign 字段，并按 key 排序）
     *
     * @param array $params 参数数组
     * @param string $key 密钥
     * @return string
     */
    public static function getSignStr(array $params, string $key): string
    {
        $sortedParams = self::sortValueRecursively($params);
        $items = [];

        foreach ($sortedParams as $k => $v) {
            if (strtolower($k) === 'sign') {
                continue;
            }

            if ($v === null || (is_string($v) && trim($v) === '')) {
                continue;
            }

            if (is_array($v)) {
                $v = json_encode($v, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            }

            $items[] = "{$k}={$v}";
        }

        $items[] = "key={$key}";
        return implode('&', $items);
    }

    /**
     * 递归排序数组或关联数组
     *
     * @param mixed $value
     * @return mixed
     */
    public static function sortValueRecursively($value)
    {
        if (is_array($value)) {
            ksort($value); // 按键排序
            foreach ($value as &$item) {
                $item = self::sortValueRecursively($item);
            }
        }

        return $value;
    }
}
