<?php

namespace App\Services;

use App\Util;

class SupefinaSpei
{
    /**
     * @var array<string,mixed>
     */
    public $config = [];

    public function __construct(string $configKey = 'SupefinaSpeiOut')
    {
        $payConfigService = new PayConfig();
        $this->config = $payConfigService->getConfig($configKey) ?? [];
    }

    /**
     * 生成随机字符串
     */
    public function generateNonceStr(int $length = 32): string
    {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        $max = strlen($chars) - 1;
        for ($i = 0; $i < $length; $i++) {
            $str .= $chars[mt_rand(0, $max)];
        }
        return $str;
    }

    /**
     * 对参数进行签名（MD5，大写），完全遵循 Supefina 文档：
     * 1. 排除 sign、null、空字符串
     * 2. 参数名 ASCII 升序
     * 3. key1=value1&key2=value2&...&key=商户密钥
     * 4. boolean 转成 "true"/"false"，与 Java toString() 一致
     *
     * @param array<string,mixed> $params
     * @return array<string,mixed>
     */
    public function sign(array $params): array
    {
        $key = trim((string)($this->config['key'] ?? ''));
        if ($key === '') {
            throw new \RuntimeException('SupefinaSpei key missing in config');
        }

        $signStr = $this->buildSignString($params, $key);
        $sign = strtoupper(md5($signStr));

        Util::WriteLog('SupefinaSpei', 'sign string (key redacted): ' . str_replace($key, '***', $signStr));

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

    /**
     * 按 Supefina 文档构建待签名字符串
     */
    private function buildSignString(array $params, string $merchantKey): string
    {
        $filtered = [];
        foreach ($params as $k => $v) {
            if (strtolower($k) === 'sign') {
                continue;
            }
            if ($v === null) {
                continue;
            }
            if ($v === '' || (is_string($v) && trim($v) === '')) {
                continue;
            }
            $filtered[$k] = $this->valueToString($v);
        }
        ksort($filtered);

        $parts = [];
        foreach ($filtered as $k => $v) {
            $parts[] = $k . '=' . $v;
        }
        $parts[] = 'key=' . $merchantKey;

        return implode('&', $parts);
    }

    /**
     * 将值转为签名字符串，与 Java toString() 行为一致
     */
    private function valueToString($v): string
    {
        if (is_bool($v)) {
            return $v ? 'true' : 'false';
        }
        if (is_array($v)) {
            return json_encode($v, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        }
        return (string)$v;
    }

    /**
     * 验证回调签名。
     *
     * @param array<string,mixed> $data
     */
    public function verify(array $data): bool
    {
        if (!isset($data['sign'])) {
            return false;
        }
        $key = trim((string)($this->config['key'] ?? ''));
        if ($key === '') {
            return false;
        }

        $sign = strtoupper(trim((string)$data['sign']));
        $params = $data;
        unset($params['sign']);

        $signStr = $this->buildSignString($params, $key);
        $expected = strtoupper(md5($signStr));

        return $sign === $expected;
    }

    /**
     * 发送 JSON POST 请求。
     *
     * @param array<string,mixed> $payload
     */
    public function postJson(string $url, array $payload, int $timeout = 20): string
    {
        $data = json_encode($payload, JSON_UNESCAPED_UNICODE);

        $headers = [
            'Content-Type: application/json',
        ];

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $result = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if (curl_errno($ch)) {
            $error = curl_error($ch);
            Util::WriteLog('SupefinaSpei_error', 'CURL Error: '.$error);
            curl_close($ch);
            throw new \RuntimeException('CURL error: '.$error);
        }

        if ($httpCode !== 200) {
            Util::WriteLog('SupefinaSpei_error', 'HTTP Code: '.$httpCode.' | Response: '.$result);
        }

        curl_close($ch);

        return (string)$result;
    }
}

