<?php

declare(strict_types=1);

namespace Kylin\RealnameGateway\Services;

use GuzzleHttp\Client;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

/**
 * NPPA 防沉迷实名验证系统网关
 *
 * 对接国家新闻出版署 wlc.nppa.gov.cn 实名认证系统，提供：
 *  - 实名认证（/identity/authentication）
 *  - 实名查询（/identity/query）
 *  - 实名校验（/identity/check）
 *
 * 合规要求：
 *  - 依据《关于防止未成年人沉迷网络游戏的通知》，所有游戏企业必须接入 NPPA
 *  - 依据《个人信息保护法》第二十八条、第五十一条，身份证号加密传输
 *  - 依据《网络安全法》第四十七条、等保 2.0 8.1.4.6，认证行为需记录审计日志
 */
class NppaGatewayService
{
    private Client $client;

    public function __construct()
    {
        $this->client = new Client([
            'base_uri' => config('kylin-realname-gateway.gateway_url'),
            'timeout' => config('kylin-realname-gateway.timeout', 8),
        ]);
    }

    /**
     * 实名认证 - 提交认证请求
     *
     * @return array{status: string, age_group?: int, message?: string}
     */
    public function verify(string $name, string $idCard, string $gameId): array
    {
        $cacheKey = "realname:verify:{$this->hashIdCard($idCard)}";

        // 缓存命中：成功缓存365±7天，失败缓存1小时
        if ($cached = Cache::get($cacheKey)) {
            return $cached;
        }

        try {
            $payload = [
                'ai' => $this->encryptIdCard($idCard),
                'name' => $name,
                'gameId' => $gameId,
            ];

            $response = $this->client->post('/identity/authentication', [
                'headers' => $this->buildHeaders($payload),
                'json' => $payload,
            ]);

            $result = json_decode($response->getBody()->getContents(), true);

            if (($result['code'] ?? -1) === 0) {
                $data = [
                    'status' => 'verified',
                    'age_group' => $result['data']['age_group'] ?? 3,
                    'message' => '认证成功',
                ];
                // 成功缓存 365±7 天
                $ttl = now()->addDays(365 + random_int(-7, 7));
                Cache::put($cacheKey, $data, $ttl);

                $this->auditVerify('verified', $gameId, $data['age_group']);
                return $data;
            }

            $data = [
                'status' => 'failed',
                'message' => $result['msg'] ?? '认证失败',
            ];
            // 失败缓存 1 小时
            Cache::put($cacheKey, $data, 3600);

            $this->auditVerify('failed', $gameId, null, $data['message']);
            return $data;

        } catch (\Exception $e) {
            Log::error('NPPA网关认证异常', [
                'error' => $e->getMessage(),
                'game_id' => $gameId,
            ]);

            $this->auditVerify('error', $gameId, null, $e->getMessage());

            return [
                'status' => 'error',
                'message' => '认证服务异常，请稍后重试',
            ];
        }
    }

    /**
     * 实名查询 - 查询已认证状态
     */
    public function query(string $idCard): array
    {
        try {
            $payload = [
                'ai' => $this->encryptIdCard($idCard),
            ];

            $response = $this->client->post('/identity/query', [
                'headers' => $this->buildHeaders($payload),
                'json' => $payload,
            ]);

            $result = json_decode($response->getBody()->getContents(), true);

            return [
                'status' => ($result['code'] ?? -1) === 0 ? 'verified' : 'unverified',
                'age_group' => $result['data']['age_group'] ?? null,
            ];
        } catch (\Exception $e) {
            Log::error('NPPA网关查询异常', ['error' => $e->getMessage()]);
            return ['status' => 'error', 'message' => '查询服务异常'];
        }
    }

    /**
     * 构建请求头（含签名）
     *
     * 依据 NPPA 接口规范，请求头需携带 appId、bizId、timestamp、sign。
     */
    private function buildHeaders(array $payload = []): array
    {
        $timestamp = (string) time();
        $sign = $this->buildSignature($payload, $timestamp);

        return [
            'Content-Type' => 'application/json',
            'appId' => config('kylin-realname-gateway.app_id'),
            'bizId' => config('kylin-realname-gateway.biz_id'),
            'timestamp' => $timestamp,
            'sign' => $sign,
        ];
    }

    /**
     * 构建请求签名（HMAC-SHA256）
     *
     * 依据 NPPA 接口规范，使用 secret_key 对请求参数和时间戳进行 HMAC-SHA256 签名，
     * 防止请求被篡改或重放。
     */
    private function buildSignature(array $payload, string $timestamp): string
    {
        $secretKey = config('kylin-realname-gateway.secret_key', '');
        $appId = config('kylin-realname-gateway.app_id', '');
        $bizId = config('kylin-realname-gateway.biz_id', '');

        // 签名串：appId + bizId + timestamp + payload_json
        $payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        $signString = $appId . $bizId . $timestamp . $payloadJson;

        return hash_hmac('sha256', $signString, $secretKey);
    }

    /**
     * 加密身份证号（传输层加密）
     *
     * 依据《个人信息保护法》第五十一条，身份证号属于敏感个人信息，
     * 传输时必须使用 RSA 公钥加密。
     */
    private function encryptIdCard(string $idCard): string
    {
        $publicKey = config('kylin-realname-gateway.public_key');
        if ($publicKey) {
            openssl_public_encrypt($idCard, $encrypted, $publicKey);
            return base64_encode($encrypted);
        }
        return $idCard;
    }

    /**
     * 身份证号哈希（用于缓存 key，避免缓存中存储身份证原文）
     */
    private function hashIdCard(string $idCard): string
    {
        return hash('sha256', $idCard);
    }

    /**
     * 记录实名认证审计日志
     *
     * 依据《网络安全法》第四十七条和等保 2.0 (GB/T 22239) 8.1.4.6，
     * 实名认证行为需记录审计日志，便于追溯。
     *
     * 审计日志写入失败不中断业务，仅记录 warning 日志。
     * 不记录身份证号原文（避免敏感信息二次泄露），仅记录认证状态和年龄段。
     */
    private function auditVerify(string $status, string $gameId, ?int $ageGroup, ?string $message = null): void
    {
        if (! class_exists(\Kylin\AuditLog\Models\AuditLog::class)) {
            return;
        }

        try {
            \Kylin\AuditLog\Models\AuditLog::create([
                'event' => 'realname_verify',
                'model_type' => 'Kylin\RealnameGateway\Services\NppaGatewayService',
                'model_id' => 0,
                'new_values' => [
                    'status' => $status,
                    'game_id' => $gameId,
                    'age_group' => $ageGroup,
                    'message' => $message,
                ],
                'old_values' => null,
                'user_id' => auth()->id(),
                'ip' => request()?->ip(),
                'url' => request()?->fullUrl(),
                'tag' => 'realname-gateway',
            ]);
        } catch (\Throwable $e) {
            logger()->warning('实名认证审计日志写入失败', [
                'error' => $e->getMessage(),
                'status' => $status,
                'game_id' => $gameId,
            ]);
        }
    }
}
