<?php

namespace app\admin\service;

use app\admin\model\User;
use think\facade\Cache;
use think\facade\Config;
use think\exception\RuntimeException;
use thans\jwt\exception\JWTException;
use thans\jwt\facade\JWT;

class JwtService
{
    /**
     * Token 过期时间（秒），优先读取 jwt 配置文件
     * @var int
     */
    private $exp;

    public function __construct()
    {
        // 从 thans/jwt 配置文件读取过期时间（config/jwt.php）
        $this->exp = Config::get('jwt.ttl', 7200);
    }

    /**
     * 生成 JWT Token（适配 thans/jwt）
     * @param User $user 用户模型实例
     * @return string
     * @throws RuntimeException
     */
    public function generateToken(User $user): string
    {
        // 严格校验参数类型
        if (!$user instanceof User) {
            throw new RuntimeException('生成Token失败：必须传入 User 模型实例');
        }

        try {
            // 构建 JWT 载荷（thans/jwt 支持数组格式）
            $payload = [
                'sub'      => $user->id,       // 主题（用户ID）
                'user_id'  => $user->id,       // 自定义字段：用户ID
                'iat'      => time(),          // 签发时间
                'exp'      => time() + $this->exp, // 过期时间
                'iss'      => Config::get('app.app_name', 'admin_system'), // 签发者
            ];

            // 生成 Token（thans/jwt 核心方法）
            $token = JWT::builder($payload);

            // 缓存 Token（区分普通用户/商家）
            $cacheKey = $user->is_store == 0
                ? "user_{$user->id}_jwt"
                : "store_{$user->id}_jwt";
            // TP 缓存：set 方法存储，过期时间与 Token 一致
            Cache::set($cacheKey, $token, $this->exp);

            return $token;
        } catch (JWTException $e) {
            throw new RuntimeException('生成JWT Token失败：'.$e->getMessage());
        }
    }

    /**
     * 验证 JWT Token 有效性（适配 thans/jwt）
     * @param string $token JWT Token 字符串（支持带/不带 Bearer 前缀）
     * @return array|null 解码后的载荷数组（无效返回null）
     */
    public function validateToken(string $token): ?array
    {
        // 预处理 Token：移除 Bearer 前缀
        $token = trim(str_replace('Bearer ', '', $token));

        if (empty($token)) {
            return null;
        }

        try {
            // 验证并解码 Token（thans/jwt 核心方法）
            $payload = JWT::verify($token);

            // 额外校验：缓存中的 Token 是否一致（防止注销/伪造）
            if (isset($payload['user_id'])) {
                $user = User::find($payload['user_id']);
                if ($user) {
                    $cacheKey = $user->is_store == 0
                        ? "user_{$user->id}_jwt"
                        : "store_{$user->id}_jwt";
                    $cacheToken = Cache::get($cacheKey);
                    // 缓存无 Token 或不一致，视为无效
                    if ($cacheToken !== $token) {
                        return null;
                    }
                }
            }

            return (array)$payload;
        } catch (JWTException $e) {
            // 捕获 thans/jwt 专属异常（过期、签名错误、格式错误等）
            return null;
        }
    }

    /**
     * 注销 Token（清除缓存）
     * @param User $user 用户模型实例
     * @return bool
     */
    public function invalidateToken(User $user): bool
    {
        $cacheKey = $user->is_store == 0
            ? "user_{$user->id}_jwt"
            : "store_{$user->id}_jwt";
        return Cache::delete($cacheKey);
    }

    /**
     * 刷新 Token（续期，thans/jwt 内置支持）
     * @param string $oldToken 旧 Token
     * @return string|null 新 Token（无效返回null）
     */
    public function refreshToken(string $oldToken): ?string
    {
        $oldToken = trim(str_replace('Bearer ', '', $oldToken));
        if (empty($oldToken)) {
            return null;
        }

        try {
            // 验证旧 Token 并生成新 Token（自动续期）
            $newToken = JWT::refresh($oldToken);

            // 更新缓存中的 Token
            $payload = JWT::verify($oldToken);
            if (isset($payload['user_id'])) {
                $user = User::find($payload['user_id']);
                if ($user) {
                    $cacheKey = $user->is_store == 0
                        ? "user_{$user->id}_jwt"
                        : "store_{$user->id}_jwt";
                    Cache::set($cacheKey, $newToken, $this->exp);
                }
            }

            return $newToken;
        } catch (JWTException $e) {
            return null;
        }
    }
}