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

use Contao\CoreBundle\Repository\WebauthnCredentialRepository;
use Doctrine\ORM\EntityManagerInterface;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Repository\WebauthnUserEntityRepository;
use Ferienpass\CoreBundle\Ux\Flash;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Translation\TranslatableMessage;

/**
 * Account-level login-method controls for members (mirrors the admin-bundle
 * SecurityController): remove the password, and toggle e-mail (magic-link)
 * sign-in. Both guard against locking the member out of their own account.
 */
class AccountSecurityController extends AbstractController
{
    /**
     * Detach the member's password. Allowed only when at least one passkey
     * exists — otherwise magic-link would be the sole sign-in method and a
     * single mailbox compromise = full account takeover.
     */
    #[Route('/account/password/remove', name: 'cms_account_remove_password', methods: ['POST'])]
    public function removePassword(
        #[CurrentUser]
        ?User $user,
        EntityManagerInterface $em,
        WebauthnUserEntityRepository $userEntities,
        #[Autowire(service: 'contao.repository.webauthn_credential')]
        WebauthnCredentialRepository $credentials,
    ): Response {
        if (!$user instanceof User) {
            throw $this->createAccessDeniedException();
        }

        // CSRF: the form carries Contao's REQUEST_TOKEN, validated globally by
        // Contao\CoreBundle\EventListener\RequestTokenListener before we get here
        // (these routes are _scope=frontend). Same model as the other account
        // POST forms (e.g. CloseAccount) — no per-controller check needed.

        $userEntity = $userEntities->findOneByUsername($user->getUsername());
        $passkeys = null !== $userEntity ? $credentials->findAllForUserEntity($userEntity) : [];

        if (\count($passkeys) < 1) {
            $this->addFlash(...Flash::error()->text(new TranslatableMessage('userAccount.loginMethods.removePassword.flash.needsPasskey', [], 'cms'))->create());

            return $this->redirectToAccount();
        }

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

        $this->addFlash(...Flash::confirmation()->text(new TranslatableMessage('userAccount.loginMethods.removePassword.flash.removed', [], 'cms'))->create());

        return $this->redirectToAccount();
    }

    /**
     * Flip per-user magic-link sign-in. Refused when turning it off would leave
     * the member with no fall-back method (no password and no passkey on file).
     */
    #[Route('/account/magic-links/toggle', name: 'cms_account_toggle_magic_links', methods: ['POST'])]
    public function toggleMagicLinks(
        #[CurrentUser]
        ?User $user,
        EntityManagerInterface $em,
        WebauthnUserEntityRepository $userEntities,
        #[Autowire(service: 'contao.repository.webauthn_credential')]
        WebauthnCredentialRepository $credentials,
    ): Response {
        if (!$user instanceof User) {
            throw $this->createAccessDeniedException();
        }

        // CSRF handled by Contao's RequestTokenListener (REQUEST_TOKEN in the
        // form); see removePassword() above.

        $turningOff = !$user->isMagicLinksDisabled();

        if ($turningOff) {
            $hasPassword = null !== $user->getPassword();
            $userEntity = $userEntities->findOneByUsername($user->getUsername());
            $passkeyCount = null !== $userEntity ? \count($credentials->findAllForUserEntity($userEntity)) : 0;

            if (!$hasPassword && $passkeyCount < 1) {
                $this->addFlash(...Flash::error()->text(new TranslatableMessage('userAccount.loginMethods.magicLink.flash.needsFallback', [], 'cms'))->create());

                return $this->redirectToAccount();
            }
        }

        $user->setMagicLinksDisabled($turningOff);
        $em->flush();

        $this->addFlash(...Flash::confirmation()->text(new TranslatableMessage(
            $turningOff ? 'userAccount.loginMethods.magicLink.flash.disabled' : 'userAccount.loginMethods.magicLink.flash.enabled',
            [],
            'cms'
        ))->create());

        return $this->redirectToAccount();
    }

    /**
     * SECURITY_REVIEW FP-024: redirect to a fixed internal route instead of the
     * attacker-controllable Referer (open redirect). The login-method controls
     * live on the "user_account" page under the "account" alias.
     */
    private function redirectToAccount(): Response
    {
        return $this->redirectToRoute('cms', ['to' => 'user_account', 'alias' => 'account']);
    }
}
