<?php
declare(strict_types=1);

namespace app\api\cache;

use support\think\Cache;

/**
 * API 用户信息 Redis 缓存
 * key = base64(user_id)，value = 加密后的用户信息 JSON
 */
class UserCache
{
    private static function prefix(): string
    {
        return config('api.user_cache_prefix', 'api:user:');
    }

    private static function expire(): int
    {
        return (int) config('api.user_cache_expire', 604800);
    }

    private static function encryptKey(): string
    {
        $key = config('api.user_encrypt_key', 'dafuweng_api_user_cache_key_32');
        return str_pad($key, 32, '0', STR_PAD_RIGHT);
    }

    /** 加密 */
    public static function encrypt(string $data): string
    {
        $key = self::encryptKey();
        $iv = substr(md5($key), 0, 16);
        return base64_encode(openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv));
    }

    /** 解密 */
    public static function decrypt(string $data): string
    {
        $key = self::encryptKey();
        $iv = substr(md5($key), 0, 16);
        $dec = openssl_decrypt(base64_decode($data, true), 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
        return $dec !== false ? $dec : '';
    }

    /**
     * 写入用户信息到 Redis
     * @param int $userId
     * @param array $userInfo 从数据库读取的用户信息（可含敏感字段，会加密存储）
     */
    public static function setUser(int $userId, array $userInfo): bool
    {
        $key = self::prefix() . base64_encode((string) $userId);
        $value = self::encrypt(json_encode($userInfo));
        return Cache::set($key, $value, self::expire());
    }

    /**
     * 从 Redis 读取用户信息
     * @return array 解密后的用户信息，不存在或失败返回空数组
     */
    public static function getUser(int $userId): array
    {
        $key = self::prefix() . base64_encode((string) $userId);
        $value = Cache::get($key);
        if ($value === null || $value === '') {
            return [];
        }
        $dec = self::decrypt($value);
        if ($dec === '') {
            return [];
        }
        $data = json_decode($dec, true);
        return is_array($data) ? $data : [];
    }

    /**
     * 仅从缓存读取用户平台币 coin（不查库，低延迟）
     * @return int|float|null 余额，缓存未命中返回 null（缓存中 coin 可能为字符串，统一转为数值）
     */
    public static function getUserCoin(int $userId): int|float|null
    {
        $user = self::getUser($userId);
        if (empty($user) || !array_key_exists('coin', $user)) {
            return null;
        }
        $coin = $user['coin'];
        if (is_int($coin) || is_float($coin)) {
            return $coin;
        }
        if (is_string($coin) && is_numeric($coin)) {
            return str_contains($coin, '.') ? (float) $coin : (int) $coin;
        }
        return null;
    }

    /** 删除用户缓存 */
    public static function deleteUser(int $userId): bool
    {
        $key = self::prefix() . base64_encode((string) $userId);
        return Cache::delete($key);
    }

    /** user-token 黑名单前缀（退出登录后使 token 失效） */
    private static function blacklistPrefix(): string
    {
        return config('api.user_cache_prefix', 'api:user:') . 'token_blacklist:';
    }

    /**
     * 将 user-token 加入黑名单（退出登录）
     * @param string $token 完整 token 字符串
     * @param int $ttl 黑名单过期时间（秒），建议为 token 剩余有效期
     */
    public static function addTokenToBlacklist(string $token, int $ttl = 86400): bool
    {
        if ($ttl <= 0) {
            return true;
        }
        $key = self::blacklistPrefix() . md5($token);
        return Cache::set($key, '1', $ttl);
    }

    /**
     * 检查 user-token 是否在黑名单中（已退出）
     */
    public static function isTokenBlacklisted(string $token): bool
    {
        $key = self::blacklistPrefix() . md5($token);
        $val = Cache::get($key);
        return $val !== null && $val !== '';
    }

    /** 当前有效 user-token 按用户存储的 key 前缀（重新登录/注册后覆盖，保证单用户单 token） */
    private static function currentTokenPrefix(): string
    {
        return config('api.user_token_current_prefix', 'api:user:current_token:');
    }

    private static function userTokenExpire(): int
    {
        return (int) config('api.user_token_exp', 604800);
    }

    /**
     * 设置该用户当前唯一有效的 user-token（登录/注册时调用，会覆盖该用户之前的 token）
     * @param int $userId 用户 ID
     * @param string $token 完整 user-token 字符串
     */
    public static function setCurrentUserToken(int $userId, string $token): bool
    {
        if ($userId <= 0 || $token === '') {
            return false;
        }
        $key = self::currentTokenPrefix() . $userId;
        return Cache::set($key, $token, self::userTokenExpire());
    }

    /**
     * 获取该用户当前在服务端登记的有效 user-token，不存在或已过期返回 null
     */
    public static function getCurrentUserToken(int $userId): ?string
    {
        if ($userId <= 0) {
            return null;
        }
        $key = self::currentTokenPrefix() . $userId;
        $value = Cache::get($key);
        return $value !== null && $value !== '' ? (string) $value : null;
    }

    /**
     * 校验请求中的 token 是否为该用户当前唯一有效 token
     */
    public static function isCurrentUserToken(int $userId, string $token): bool
    {
        $current = self::getCurrentUserToken($userId);
        return $current !== null && $current === $token;
    }

    /** 按 username 的登录会话 key 前缀（token 中间件：存在即视为已登录） */
    private static function sessionUsernamePrefix(): string
    {
        return config('api.session_username_prefix', 'api:user:session:');
    }

    private static function sessionExpire(): int
    {
        return (int) config('api.session_expire', 604800);
    }

    /** 设置 username 当前有效 token（JWT），重新登录会覆盖，实现单点登录 */
    public static function setSessionByUsername(string $username, string $token): bool
    {
        if ($username === '' || $token === '') {
            return false;
        }
        $key = self::sessionUsernamePrefix() . $username;
        return Cache::set($key, $token, self::sessionExpire());
    }

    /** 获取 username 当前在服务端登记的有效 token（JWT），不存在返回 null */
    public static function getSessionTokenByUsername(string $username): ?string
    {
        if ($username === '') {
            return null;
        }
        $key = self::sessionUsernamePrefix() . $username;
        $val = Cache::get($key);
        return $val !== null && $val !== '' ? (string) $val : null;
    }

    /** 检查 username 是否已有登录会话（Redis 中是否存在当前 token） */
    public static function hasSessionByUsername(string $username): bool
    {
        return self::getSessionTokenByUsername($username) !== null;
    }

    /** 删除 username 登录会话（退出登录时调用） */
    public static function deleteSessionByUsername(string $username): bool
    {
        if ($username === '') {
            return false;
        }
        $key = self::sessionUsernamePrefix() . $username;
        return Cache::delete($key);
    }

    /** 玩家缓存 key 前缀（Token 中间件用，减少重复查库） */
    private static function playerCachePrefix(): string
    {
        return config('api.player_cache_prefix', 'api:player:');
    }

    private static function playerCacheTtl(): int
    {
        return (int) config('api.player_cache_ttl', 300);
    }

    /** /api/v1/getPlayerInfo 快照 key 前缀 */
    private static function playerInfoSnapshotPrefix(): string
    {
        return config('api.player_info_snapshot_prefix', 'api:v1:player_info:');
    }

    private static function playerInfoSnapshotTtl(): int
    {
        return (int) config('api.player_info_snapshot_ttl', 180);
    }

    /**
     * 按 username 缓存玩家信息（仅 id + username，供中间件注入 request->player 后使用）
     * 登录/信息变更时需调用 deletePlayerByUsername 失效
     */
    public static function setPlayerByUsername(string $username, array $playerRow): bool
    {
        if ($username === '' || empty($playerRow)) {
            return false;
        }
        $ttl = self::playerCacheTtl();
        if ($ttl <= 0) {
            return true;
        }
        $key = self::playerCachePrefix() . $username;
        return Cache::set($key, json_encode($playerRow), $ttl);
    }

    /** 按 username 取缓存玩家，未命中返回 null */
    public static function getPlayerByUsername(string $username): ?array
    {
        if ($username === '') {
            return null;
        }
        if (self::playerCacheTtl() <= 0) {
            return null;
        }
        $key = self::playerCachePrefix() . $username;
        $val = Cache::get($key);
        if ($val === null || $val === '') {
            return null;
        }
        $data = json_decode((string) $val, true);
        return is_array($data) ? $data : null;
    }

    /** 退出登录或玩家信息变更时删除玩家缓存 */
    public static function deletePlayerByUsername(string $username): bool
    {
        if ($username === '') {
            return false;
        }
        $key = self::playerCachePrefix() . $username;
        $r1 = Cache::delete($key);
        $snapKey = self::playerInfoSnapshotPrefix() . $username;
        $r2 = Cache::delete($snapKey);
        return $r1 || $r2;
    }

    /**
     * 读取 getPlayerInfo 接口用的玩家公开信息快照（未命中返回 null）
     * @return array<string, mixed>|null
     */
    public static function getPlayerInfoSnapshotByUsername(string $username): ?array
    {
        if ($username === '') {
            return null;
        }
        if (self::playerInfoSnapshotTtl() <= 0) {
            return null;
        }
        $key = self::playerInfoSnapshotPrefix() . $username;
        $val = Cache::get($key);
        if ($val === null || $val === '') {
            return null;
        }
        $data = json_decode((string) $val, true);
        if (!is_array($data) || !array_key_exists('username', $data)) {
            return null;
        }
        return $data;
    }

    /**
     * 写入 getPlayerInfo 返回体快照（已脱敏数组）
     * @param array<string, mixed> $info
     */
    public static function setPlayerInfoSnapshotByUsername(string $username, array $info): bool
    {
        if ($username === '' || empty($info)) {
            return false;
        }
        $ttl = self::playerInfoSnapshotTtl();
        if ($ttl <= 0) {
            return true;
        }
        $key = self::playerInfoSnapshotPrefix() . $username;
        $payload = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
        if ($payload === false) {
            return false;
        }
        return Cache::set($key, $payload, $ttl);
    }
}
