<?php
declare(strict_types=1);

namespace app\api\middleware;

use app\api\cache\UserCache;
use app\api\util\ReturnCode;
use app\dice\model\player\DicePlayer;
use plugin\saiadmin\exception\ApiException;
use Tinywan\Jwt\JwtToken;
use Tinywan\Jwt\Exception\JwtTokenException;
use Tinywan\Jwt\Exception\JwtTokenExpiredException;
use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;

/**
 * 校验 token 请求头（JWT）
 * 解码 JWT 取 username，与 Redis 中当前有效 token 比对；不一致则旧 token 已失效，请重新登录
 * 通过后注入 request->player_id、request->player
 */
class TokenMiddleware implements MiddlewareInterface
{
    public function process(Request $request, callable $handler): Response
    {
        $token = $request->header('token');
        if ($token === null || $token === '') {
            $auth = $request->header('authorization');
            if ($auth && stripos($auth, 'Bearer ') === 0) {
                $token = trim(substr($auth, 7));
            }
        }
        $token = $token !== null ? trim((string) $token) : '';
        if ($token === '') {
            throw new ApiException('Please provide token', ReturnCode::UNAUTHORIZED);
        }

        try {
            $decoded = JwtToken::verify(1, $token);
        } catch (JwtTokenExpiredException $e) {
            throw new ApiException('Token expired, please login again', ReturnCode::TOKEN_INVALID);
        } catch (JwtTokenException $e) {
            throw new ApiException('Invalid or expired token', ReturnCode::TOKEN_INVALID);
        } catch (\Throwable $e) {
            throw new ApiException('Token format invalid', ReturnCode::TOKEN_INVALID);
        }

        $extend = $decoded['extend'] ?? [];
        if ((string) ($extend['plat'] ?? '') !== 'api_login') {
            throw new ApiException('Invalid or expired token', ReturnCode::TOKEN_INVALID);
        }
        $username = trim((string) ($extend['username'] ?? ''));
        if ($username === '') {
            throw new ApiException('Invalid or expired token', ReturnCode::TOKEN_INVALID);
        }
        $userId = (int) ($extend['id'] ?? 0);
        if ($userId <= 0) {
            throw new ApiException('Invalid or expired token', ReturnCode::TOKEN_INVALID);
        }

        $currentToken = UserCache::getCurrentUserToken($userId);
        if ($currentToken === null || $currentToken === '') {
            $player = DicePlayer::find($userId);
            if (!$player) {
                throw new ApiException('Please register', ReturnCode::TOKEN_INVALID);
            }
            throw new ApiException('Please login again', ReturnCode::TOKEN_INVALID);
        }
        if ($currentToken !== $token) {
            throw new ApiException('Please login again (account logged in elsewhere)', ReturnCode::TOKEN_INVALID);
        }

        // 优先从 Redis 缓存取玩家，避免每次请求都查库
        $player = null;
        $cached = UserCache::getUser($userId);
        if (!empty($cached) && isset($cached['id']) && (int) $cached['id'] === $userId) {
            $player = (new DicePlayer())->data($cached, true);
        }
        if ($player === null) {
            $player = DicePlayer::find($userId);
            if (!$player) {
                UserCache::deleteSessionByUsername($username);
                throw new ApiException('Please login again', ReturnCode::TOKEN_INVALID);
            }
            UserCache::setUser($userId, $player->hidden(['password'])->toArray());
        }
        $request->player_id = (int) $player->id;
        $request->player = $player;
        return $handler($request);
    }
}
