<?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\CmsBundle\Controller\Fragment;

use Contao\CoreBundle\Controller\AbstractFragmentController;
use Ferienpass\CmsBundle\Form\ResetPasswordRequestFormType;
use Ferienpass\CmsBundle\Message\ResetPassword;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;

class LostPasswordController extends AbstractFragmentController
{
    use ResetPasswordControllerTrait;

    public function __construct(
        private readonly MessageBusInterface $commandBus,
        private readonly RateLimiterFactoryInterface $authEmailLimiter,
        private readonly LoggerInterface $logger,
    ) {
    }

    public function __invoke(Request $request): Response
    {
        $form = $this->createForm(ResetPasswordRequestFormType::class);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            // Per-IP throttle on top of SymfonyCasts' per-user limit, to stop
            // spraying reset mails across many addresses (SECURITY_REVIEW FP-016).
            // Respond identically whether or not we dispatched, to avoid leaking
            // throttle/enumeration state.
            if ($this->authEmailLimiter->create($request->getClientIp() ?? '')->consume(1)->isAccepted()) {
                $this->commandBus->dispatch(new ResetPassword($form->get('email')->getData()));
            } else {
                // The limiter is per client IP and shared with the magic-link
                // endpoint, so a NAT'd network (school, municipality, mobile
                // carrier) can burn the budget on behalf of unrelated parents.
                // They still see "check your e-mail"; record it so the drop is
                // at least diagnosable.
                $this->logger->warning('Password reset requested: {outcome}', [
                    'outcome' => 'rate_limited',
                    'email' => $form->get('email')->getData(),
                    'ip' => $request->getClientIp(),
                    'scope' => 'cms',
                ]);
            }

            return $this->redirectToRoute('cms_lost_password_requested');
        }

        /** @noinspection FormViewTemplate */
        return $this->render('@Contao/fragment/reset_password/request.html.twig', [
            'form' => $form,
        ]);
    }
}
