<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Service;

use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;

class OfferPrivateLinkService
{
    /**
     * Truncation length (bytes) of the HMAC used as the URL signature. 9
     * bytes → 12 base64url chars (no padding), i.e. 72 bits — far beyond
     * what an online, rate-limited guessing attack could brute-force, while
     * keeping the link short. The signature can't be precomputed offline
     * anyway: it needs the offer's server-side randomSeed.
     */
    private const SIGNATURE_BYTES = 9;

    /**
     * Short signature for an offer's private link: base64url of a truncated
     * HMAC-SHA256 keyed by the offer's secret randomSeed over its UUID. The
     * UUID is public (it's the lookup handle in the URL); the seed is not, so
     * the signature can't be derived from the public /o URL. Empty string for
     * an offer with no seed (pre-backfill) → no working link.
     */
    public function signature(OfferInterface $offer): string
    {
        $seed = (string) $offer->getRandomSeed();
        if ('' === $seed) {
            return '';
        }

        $hmac = hash_hmac('sha256', $offer->getUuid()->toBinary(), $seed, true);

        return rtrim(strtr(base64_encode(substr($hmac, 0, self::SIGNATURE_BYTES)), '+/', '-_'), '=');
    }

    /**
     * Constant-time check of a signature presented in a private-link URL.
     */
    public function isValidSignature(OfferInterface $offer, string $signature): bool
    {
        $expected = $this->signature($offer);

        return '' !== $expected && hash_equals($expected, $signature);
    }

    /**
     * Route parameters for the private link: the base58 UUID (lookup handle)
     * plus the short signature (the secret gate).
     *
     * @return array{uuid: string, sig: string}
     */
    public function urlParameters(OfferInterface $offer): array
    {
        return [
            'uuid' => $offer->getUuid()->toBase58(),
            'sig' => $this->signature($offer),
        ];
    }
}
