<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\services;

use apexl\Io\modules\user\Operator\RefreshTokenOperator;

/**
 * Housekeeping for `refresh_tokens`.
 *
 * Rows are per session (device / app / tab), so the table grows with every
 * login and nothing ever removes the rows whose expiry has passed — they are
 * dead weight that also keeps a (hashed) credential on disk long after it
 * stopped being usable.
 *
 * This package ships no scheduler, so the purge is exposed as a service and as
 * the `user:purge-refresh-tokens` console command. Consuming projects should
 * run it daily: either from their own cron module (io-cron's
 * `cronModule::HOOK__REGISTER_CRON_TASK`) or from the system crontab via
 * `vendor/bin/daedalus user:purge-refresh-tokens`.
 */
final readonly class ExpiredRefreshTokenPurger
{
    /**
     * Keep expired rows for a day before deleting them. Refresh is a
     * once-per-access-token-lifetime call, so a row that expired minutes ago may
     * still be presented by a client coming back from sleep; the grace only
     * affects how long the dead row lingers, never whether it is accepted
     * (RefreshTokenEntityFinder rejects an expired row regardless).
     */
    public const int DEFAULT_GRACE_SECONDS = 86400;

    public function __construct(private RefreshTokenOperator $operator) {}

    /**
     * Delete refresh tokens that expired more than $graceSeconds ago.
     *
     * @return int the number of rows removed
     */
    public function purge(int $graceSeconds = self::DEFAULT_GRACE_SECONDS): int
    {
        $expiredBefore = time() - max(0, $graceSeconds);

        // Counted before the delete: Vault's delete builder reports no row count.
        $count = $this->operator->countExpiredBefore($expiredBefore);

        if ($count > 0) {
            $this->operator->deleteExpiredBefore($expiredBefore);
        }

        return $count;
    }
}
