<?php

/**
 * 文件功能：NovaLink 支付中心客户端
 * 负责读取聊天室后台配置，按支付中心要求生成签名并发起下单、查单等请求
 */

namespace App\Services;

use App\Models\Sysparam;
use App\Models\VipPaymentOrder;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use RuntimeException;

/**
 * NovaLink 支付中心客户端
 * 负责构建开放接口签名请求，并与远端支付中心完成下单和查单通信。
 */
class VipPaymentCenterClient
{
    /**
     * 判断 VIP 在线支付是否已启用
     */
    public function isEnabled(): bool
    {
        return Sysparam::getValue('vip_payment_enabled', '0') === '1';
    }

    /**
     * 判断支付中心接入配置是否完整
     */
    public function hasValidConfig(): bool
    {
        $config = $this->getConfig();

        return $config['base_url'] !== ''
            && $config['app_key'] !== ''
            && $config['app_secret'] !== '';
    }

    /**
     * 获取当前支付中心配置
     *
     * @return array{base_url:string,app_key:string,app_secret:string,timeout:int}
     */
    public function getConfig(): array
    {
        return [
            'base_url' => $this->normalizeBaseUrl(Sysparam::getValue('vip_payment_base_url', '')),
            'app_key' => Sysparam::getValue('vip_payment_app_key', ''),
            'app_secret' => Sysparam::getValue('vip_payment_app_secret', ''),
            'timeout' => (int) Sysparam::getValue('vip_payment_timeout', '10'),
        ];
    }

    /**
     * 为本地 VIP 订单在支付中心创建远端支付单
     *
     * @param  VipPaymentOrder  $vipPaymentOrder  本地 VIP 支付订单
     * @param  string  $syncReturnUrl  同步回调地址
     * @param  string  $asyncNotifyUrl  异步回调地址
     * @return array<string, mixed>
     */
    public function createOrder(VipPaymentOrder $vipPaymentOrder, string $syncReturnUrl, string $asyncNotifyUrl): array
    {
        $payload = [
            'merchant_order_no' => $vipPaymentOrder->merchant_order_no,
            'subject' => $vipPaymentOrder->subject,
            'body' => '聊天室 VIP 会员购买：'.$vipPaymentOrder->vip_name,
            'amount' => number_format((float) $vipPaymentOrder->amount, 2, '.', ''),
            // 支付中心要求商户显式指定 provider，不能依赖平台自动推断。
            'provider' => $vipPaymentOrder->provider,
            'business_type' => 'chatroom_vip',
            'sync_return_url' => $syncReturnUrl,
            'async_notify_url' => $asyncNotifyUrl,
            'meta' => [
                'user_id' => $vipPaymentOrder->user_id,
                'vip_level_id' => $vipPaymentOrder->vip_level_id,
                'local_order_no' => $vipPaymentOrder->order_no,
            ],
        ];

        return $this->request('POST', '/api/open/v1/pay/orders', $payload);
    }

    /**
     * 查询支付中心订单状态
     *
     * @param  string  $paymentOrderNo  远端平台支付单号
     * @return array<string, mixed>
     */
    public function queryOrder(string $paymentOrderNo): array
    {
        return $this->request('GET', '/api/open/v1/pay/orders/'.$paymentOrderNo);
    }

    /**
     * 校验支付中心异步回调签名
     *
     * @param  string  $signature  请求头携带的签名
     * @param  string  $rawBody  原始请求体 JSON
     */
    public function isValidWebhookSignature(string $signature, string $rawBody): bool
    {
        $config = $this->getConfig();

        if ($signature === '' || $config['app_secret'] === '') {
            return false;
        }

        // 支付中心回调直接使用 JSON 原文做 HMAC，避免数组重组后签名不一致。
        $expectedSignature = hash_hmac('sha256', $rawBody, $config['app_secret']);

        return hash_equals($expectedSignature, $signature);
    }

    /**
     * 调用支付中心开放接口
     *
     * @param  string  $method  请求方法
     * @param  string  $path  接口路径
     * @param  array<string, mixed>  $payload  请求体
     * @return array<string, mixed>
     */
    private function request(string $method, string $path, array $payload = []): array
    {
        if (! $this->isEnabled() || ! $this->hasValidConfig()) {
            throw new RuntimeException('VIP 支付中心尚未完成配置。');
        }

        $config = $this->getConfig();
        $body = strtoupper($method) === 'GET'
            ? ''
            : json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        // 先按 NovaLink 约定计算签名头，再发起请求。
        $headers = $this->buildHeaders($method, $path, $body, $config['app_key'], $config['app_secret']);
        $request = Http::timeout(max($config['timeout'], 3))
            ->acceptJson()
            ->withHeaders($headers);

        $response = strtoupper($method) === 'GET'
            ? $request->get($config['base_url'].$path)
            : $request->withBody($body, 'application/json')->send($method, $config['base_url'].$path);

        return $this->parseResponse($response);
    }

    /**
     * 构建开放 API 认证请求头
     *
     * @param  string  $method  请求方法
     * @param  string  $path  接口路径
     * @param  string  $body  JSON 请求体
     * @param  string  $appKey  接入应用 Key
     * @param  string  $appSecret  接入应用 Secret
     * @return array<string, string>
     */
    private function buildHeaders(string $method, string $path, string $body, string $appKey, string $appSecret): array
    {
        $timestamp = (string) now()->timestamp;
        $nonce = (string) str()->uuid();

        // 保持与 NovaLink 中间件完全一致的签名原文拼接顺序。
        $signString = implode("\n", [
            strtoupper($method),
            $path,
            $timestamp,
            $nonce,
            hash('sha256', $body),
        ]);

        return [
            'X-App-Key' => $appKey,
            'X-Timestamp' => $timestamp,
            'X-Nonce' => $nonce,
            'X-Signature' => hash_hmac('sha256', $signString, $appSecret),
        ];
    }

    /**
     * 解析支付中心接口响应
     *
     * @param  Response  $response  HTTP 响应对象
     * @return array<string, mixed>
     */
    private function parseResponse(Response $response): array
    {
        if (! $response->successful()) {
            throw new RuntimeException('支付中心请求失败：'.$response->body());
        }

        $json = $response->json();

        if (! is_array($json) || ! ($json['success'] ?? false)) {
            throw new RuntimeException((string) ($json['message'] ?? '支付中心返回了无效响应。'));
        }

        return (array) ($json['data'] ?? []);
    }

    /**
     * 规范化支付中心地址
     *
     * @param  string  $baseUrl  后台录入的地址
     */
    private function normalizeBaseUrl(string $baseUrl): string
    {
        return rtrim(trim($baseUrl), '/');
    }
}
