<?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\Validator;

use Ferienpass\CmsBundle\Dto\FriendCodeDto;
use Ferienpass\CoreBundle\Entity\FriendCode;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;

class FriendCodeValidator extends ConstraintValidator
{
    public function __construct(private readonly RateLimiterFactoryInterface $friendCodeLimiter, public readonly RequestStack $requestStack)
    {
    }

    public function validate(mixed $value, Constraint $constraint): void
    {
        if (!$constraint instanceof ValidFriendCode) {
            throw new UnexpectedTypeException($constraint, ValidFriendCode::class);
        }

        if (null === $value) {
            return;
        }

        if (!$value instanceof FriendCodeDto) {
            throw new UnexpectedValueException($value, FriendCodeDto::class);
        }

        $code = trim($value->getCode() ?? '');
        if ('' === $code) {
            $this->context->buildViolation($constraint->messageEmpty)
                ->atPath('code')
                ->addViolation()
            ;

            return;
        }

        // Brute-forcing friend codes
        $limiter = $this->friendCodeLimiter->create(\sprintf('%d-%s', $value->getOffer()->getId(), $this->requestStack->getCurrentRequest()?->getClientIp() ?? ''));
        if (false === $limiter->consume(1)->isAccepted()) {
            $this->context->buildViolation('Bitte probieren Sie in 24 Stunden erneut.')
                ->atPath('code')
                ->addViolation()
            ;

            return;
        }

        if (null !== $value->getOffer()->getFriendCodes()->findFirst(static fn (int $i, FriendCode $c) => $c->getCode() === $code)) {
            return;
        }

        $this->context->buildViolation($constraint->messageNotFound)
            ->setParameter('{{ string }}', $code)
            ->atPath('code')
            ->addViolation()
        ;
    }
}
