<?php

declare(strict_types=1);

namespace Kylin\Ecommerce\Services;

/**
 * 支付安全验证服务
 *
 * 依据 PCI DSS (支付卡行业数据安全标准)：
 * - 禁止存储完整的 CVV/CVC 码
 * - 禁止存储完整的卡号（仅保留后 4 位）
 * - 支付数据传输需加密
 *
 * 依据《中华人民共和国网络安全法》第四十二条：
 * 不得泄露、篡改、毁损其收集的个人信息
 *
 * 依据《中华人民共和国个人信息保护法》第二十八条：
 * 支付信息属于敏感个人信息，需严格保护
 *
 * 使用方式：
 *   $result = PaymentSecurityValidator::sanitize($paymentData);
 *   $result = PaymentSecurityValidator::validateCallback($callbackData);
 */
final class PaymentSecurityValidator
{
    /**
     * 敏感支付字段（禁止存储）
     */
    private const PROHIBITED_FIELDS = [
        'cvv', 'cvc', 'cvv2', 'cvc2', 'cvn', 'cid',
        'card_security_code', 'security_code',
        'full_card_number', 'card_number',
        'pin', 'payment_password',
    ];

    /**
     * 需脱敏的支付字段
     */
    private const MASKABLE_FIELDS = [
        'card_number', 'bank_card_no', 'account_number',
        'phone', 'mobile', 'id_card',
    ];

    /**
     * 清理支付数据中的敏感字段
     *
     * 依据 PCI DSS 要求，禁止在应用层存储 CVV 和完整卡号。
     *
     * @param array $paymentData 支付数据
     * @return array{cleaned: array, removed_fields: array, warnings: array}
     */
    public static function sanitize(array $paymentData): array
    {
        $cleaned = $paymentData;
        $removedFields = [];
        $warnings = [];

        // 移除禁止存储的字段
        foreach (self::PROHIBITED_FIELDS as $field) {
            if (array_key_exists($field, $cleaned)) {
                $removedFields[] = $field;
                unset($cleaned[$field]);
                $warnings[] = "已移除敏感支付字段 [{$field}]，依据 PCI DSS 禁止存储";
            }
        }

        // 脱敏可存储的支付字段
        foreach (self::MASKABLE_FIELDS as $field) {
            if (isset($cleaned[$field]) && is_string($cleaned[$field])) {
                $cleaned[$field] = self::maskCardNumber($cleaned[$field]);
                $warnings[] = "支付字段 [{$field}] 已脱敏（仅保留后 4 位）";
            }
        }

        // 检查是否有敏感字段未清理
        if (!empty($removedFields)) {
            logger()->warning('支付数据中包含禁止存储的敏感字段', [
                'removed_fields' => $removedFields,
                'standard' => 'PCI DSS',
                'timestamp' => now()->toDateTimeString(),
            ]);
        }

        return [
            'cleaned' => $cleaned,
            'removed_fields' => $removedFields,
            'warnings' => $warnings,
        ];
    }

    /**
     * 验证支付回调签名
     *
     * 依据《电子商务法》，支付回调需验证签名防止伪造。
     *
     * @param array $callbackData 回调数据
     * @param string $signature 回调签名
     * @param string $channel 支付渠道（alipay/wechat/unipay）
     * @return bool
     */
    public static function validateCallback(array $callbackData, string $signature, string $channel = 'default'): bool
    {
        // 根据支付渠道选择验证方式
        return match ($channel) {
            'alipay' => self::validateAlipayCallback($callbackData, $signature),
            'wechat' => self::validateWechatCallback($callbackData, $signature),
            'unipay' => self::validateUnipayCallback($callbackData, $signature),
            default => self::validateDefaultCallback($callbackData, $signature),
        };
    }

    /**
     * 验证支付宝回调签名
     */
    private static function validateAlipayCallback(array $data, string $signature): bool
    {
        // 支付宝 RSA 签名验证
        $publicKey = config('kylin-ecommerce.payment.alipay_public_key', '');
        if (empty($publicKey)) {
            logger()->warning('支付宝公钥未配置，跳过签名验证');
            return false;
        }

        unset($data['sign'], $data['sign_type']);

        ksort($data);
        $signStr = urldecode(http_build_query($data));

        $result = openssl_verify(
            $signStr,
            base64_decode($signature),
            $publicKey,
            OPENSSL_ALGO_SHA256
        );

        return $result === 1;
    }

    /**
     * 验证微信支付回调签名
     */
    private static function validateWechatCallback(array $data, string $signature): bool
    {
        $apiKey = config('kylin-ecommerce.payment.wechat_api_key', '');
        if (empty($apiKey)) {
            logger()->warning('微信支付 API Key 未配置，跳过签名验证');
            return false;
        }

        unset($data['sign']);

        ksort($data);
        $signStr = urldecode(http_build_query($data)) . '&key=' . $apiKey;

        $expectedSign = strtoupper(md5($signStr));

        return hash_equals($expectedSign, strtoupper($signature));
    }

    /**
     * 验证银联支付回调签名
     */
    private static function validateUnipayCallback(array $data, string $signature): bool
    {
        $certPath = config('kylin-ecommerce.payment.unipay_cert_path', '');
        if (empty($certPath) || !file_exists($certPath)) {
            logger()->warning('银联证书未配置，跳过签名验证');
            return false;
        }

        unset($data['signature'], $data['signMethod']);

        ksort($data);
        $signStr = urldecode(http_build_query($data));

        $cert = file_get_contents($certPath);
        $publicKey = openssl_get_publickey($cert);

        $result = openssl_verify(
            hash('sha256', $signStr, true),
            base64_decode($signature),
            $publicKey,
            'sha256'
        );

        return $result === 1;
    }

    /**
     * 默认回调验证
     */
    private static function validateDefaultCallback(array $data, string $signature): bool
    {
        $appKey = config('app.key');
        unset($data['sign'], $data['signature']);

        ksort($data);
        $signStr = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        $expectedSign = hash_hmac('sha256', $signStr, $appKey);

        return hash_equals($expectedSign, $signature);
    }

    /**
     * 脱敏卡号（仅保留后 4 位）
     *
     * @param string $cardNumber 卡号
     * @return string 脱敏后的卡号
     */
    public static function maskCardNumber(string $cardNumber): string
    {
        $length = mb_strlen($cardNumber);
        if ($length <= 4) {
            return '****';
        }
        return '****' . mb_substr($cardNumber, -4);
    }

    /**
     * 验证支付金额一致性
     *
     * 防止支付金额被篡改。
     *
     * @param float $orderAmount 订单金额
     * @param float $callbackAmount 回调金额
     * @return bool
     */
    public static function validateAmount(float $orderAmount, float $callbackAmount): bool
    {
        $tolerance = 0.01; // 1 分钱容差
        return abs($orderAmount - $callbackAmount) < $tolerance;
    }
}