<?php

namespace App\Domain\Services;

use App\Domain\Entities\RefreshToken;
use App\Domain\Repositories\TokenRepositoryInterface;
use App\Domain\Repositories\UserRepositoryInterface;
use App\Exceptions\AuthException;
use App\Infrastructure\Security\JwtManager;
use App\Support\Str;
use App\Support\Uuid;

class TokenService
{
    public function __construct(
        private JwtManager $jwt,
        private TokenRepositoryInterface $tokens,
        private UserRepositoryInterface $users
    ) {
    }

    public function issuePair(int $userId, int $roleId): array
    {
        $access = $this->jwt->issue($userId, $roleId);
        $refresh = Str::random(64);
        $expiresAt = time() + (int)($_ENV['JWT_REFRESH_EXPIRATION'] ?? 604800);

        $this->tokens->storeRefreshToken(new RefreshToken(
            Uuid::v4(),
            $userId,
            hash('sha256', $refresh),
            $expiresAt
        ));

        return [
            'token' => $access['token'],
            'access_token' => $access['token'],
            'refresh_token' => $refresh,
            'token_type' => 'Bearer',
            'expires_in' => (int)($_ENV['JWT_EXPIRATION'] ?? 3600),
        ];
    }

    public function refresh(string $refreshToken): array
    {
        $stored = $this->tokens->findRefreshToken(hash('sha256', $refreshToken));
        if (!$stored || $stored->revoked || $stored->expiresAt < time()) {
            throw new AuthException('Invalid refresh token', 401);
        }

        $this->tokens->revokeRefreshToken($stored->tokenHash);
        $user = $this->users->findById($stored->userId);
        if (!$user) {
            throw new AuthException('User not found', 404);
        }

        return $this->issuePair((int)$user->id, $user->roleId);
    }

    public function validate(string $token): object
    {
        $decoded = $this->jwt->decode($token);
        if (!empty($decoded->jti) && $this->tokens->isJtiBlacklisted($decoded->jti)) {
            throw new AuthException('Token has been revoked', 401);
        }

        return $decoded;
    }

    public function revoke(string $accessToken): void
    {
        $decoded = $this->jwt->decode($accessToken);
        if (empty($decoded->jti) || empty($decoded->exp)) {
            throw new AuthException('Invalid token structure', 401);
        }

        $this->tokens->blacklistJti($decoded->jti, (int)$decoded->exp);
    }

    public function jwks(): array
    {
        return $this->jwt->publicJwks();
    }

    public function publicKey(): ?string
    {
        return $this->jwt->publicKey();
    }
}
