<?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\AdminBundle\Controller\Page;

use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\AdminBundle\Form\AcceptInvitationType;
use Ferienpass\CoreBundle\Entity\Host;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Repository\UserRepository;
use Ferienpass\CoreBundle\Ux\Flash;
use FOS\HttpCacheBundle\CacheManager;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\UriSigner;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;

#[Route('/einladung/{email}/{host}', name: 'admin_invitation')]
final class FollowInvitationController extends AbstractController
{
    public function __construct(private readonly UserPasswordHasherInterface $passwordHasher)
    {
    }

    public function __invoke(string $email, #[CurrentUser] ?User $user, #[MapEntity(mapping: ['host' => 'alias'])] Host $host, UserRepository $userRepository, EntityManagerInterface $em, Request $request, UriSigner $uriSigner, CacheManager $cacheManager): Response
    {
        if (!$uriSigner->checkRequest($request)) {
            throw $this->createNotFoundException();
        }

        $createUser = !$user instanceof User;
        $user ??= new User();

        $user->addAccountRole('ROLE_HOST');

        $form = $this->createForm(AcceptInvitationType::class, $user, ['create_user' => $createUser]);

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            // The invite page is cached in the reverse proxy (1h s-maxage, set
            // below). The invitation is now being consumed, so BAN that URL to
            // evict the stale "you've been invited" page immediately instead of
            // letting it linger. The acceptance POST hits the same URL as the
            // cached GET, so we ban this request's path (raw, to match the
            // proxy's `X-Url = bereq.url` encoding) across any `?_hash` variant.
            // Scoped to this tenant's host so it never evicts another tenant.
            $cachedPath = strtok($request->getRequestUri(), '?');
            $cacheManager->invalidateRegex('^'.preg_quote($cachedPath).'(\?|$)', hosts: [$request->getHost()]);

            if (!$user->getId()) {
                $user->setPassword($this->passwordHasher->hashPassword($user, $user->getPlainPassword() ?? ''));
                $host->addMember($user);

                $this->addFlash(...Flash::confirmation()->text('Account erstellt. Bitte melden Sie sich nun mit Ihrer E-Mail-Adresse an.')->create());

                $em->persist($user);
                $em->flush();

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

            $host->addMember($user);
            $em->flush();

            $this->addFlash(...Flash::confirmation()->text('Einladung angenommen')->create());

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

        /** @noinspection FormViewTemplate `createView()` messes ups error handling/redirect */
        $response = $this->render('@FerienpassAdmin/page/login/follow_invitation.html.twig', [
            'host' => $host,
            'form' => $form,
            'invitee_email' => $email,
        ]);

        // The invite page is a pure function of its signed URL (email + host) —
        // the URL is unguessable (UriSigner->checkRequest above), so it's safe to
        // share. Cache it in the reverse proxy for an hour. Acceptance BANs this
        // URL explicitly (above), so the hour is just a fallback bound for the
        // revoked-but-never-accepted case. Partition by the user-context hash so
        // an authenticated follower (who sees a different form) never receives an
        // anonymous visitor's cached copy. POST submits are never proxy-cached.
        $response->setPublic();
        $response->setSharedMaxAge(3600);
        $response->setVary('X-User-Context-Hash', false);

        return $response;
    }
}
