<?php

declare(strict_types=1);

namespace App\Application\Auth;

use Spiral\Auth\TokenInterface;
use Spiral\Auth\TokenStorageInterface;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;

final class JWTTokenStorage implements TokenStorageInterface
{
    /** @var callable */
    private $time;

    public function __construct(
        private readonly string $secret,
        private readonly string $algorithm = 'HS256',
        private readonly string $expiresAt = '+30 days',
        callable $time = null,
    ) {
        $this->time = $time ?? static fn(string $offset): \DateTimeImmutable => new \DateTimeImmutable($offset);
    }

    public function load(string $id): ?TokenInterface
    {
        try {
            $token = (array) JWT::decode($id, new Key($this->secret, $this->algorithm));
        } catch (ExpiredException $exception) {
            throw $exception;
        } catch (\Throwable) {
            return null;
        }

        if (
            false === isset($token['data'])
            || false === isset($token['iat'])
            || false === isset($token['exp'])
        ) {
            return null;
        }

        return new Token(
            $id,
            $token,
            (array) $token['data'],
            (new \DateTimeImmutable())->setTimestamp($token['iat']),
            (new \DateTimeImmutable())->setTimestamp($token['exp']),
        );
    }

    public function create(array|\JsonSerializable $payload, \DateTimeInterface $expiresAt = null): TokenInterface
    {
        $issuedAt = ($this->time)('now');
        $expiresAt = $expiresAt ?? ($this->time)($this->expiresAt);
        $token = [
            'iat' => $issuedAt->getTimestamp(),
            'exp' => $expiresAt->getTimestamp(),
            'data' => $payload,
        ];

        return new Token(
            JWT::encode($token, $this->secret, $this->algorithm),
            $token,
            $payload,
            $issuedAt,
            $expiresAt,
        );
    }

    public function delete(TokenInterface $token): void
    {
        // We don't need to do anything here since JWT tokens are self-contained.
    }
}
