<?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 Contao\CoreBundle\Entity\WebauthnCredential;
use Contao\CoreBundle\Repository\WebauthnCredentialRepository;
use Ferienpass\CmsBundle\EventListener\WebauthnCredentialNamer;
use Ferienpass\CoreBundle\Entity\User;
use Ferienpass\CoreBundle\Repository\WebauthnUserEntityRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;

/**
 * Endpoints that back the passkeys section of /admin/sicherheit.
 *
 * Naming a freshly-created credential reuses the cms-bundle entity listener
 * `WebauthnCredentialNamer` (it's a Doctrine pre-persist hook tied to the
 * credential entity, firewall-agnostic — same session key works here).
 */
final class PasskeyAccountController extends AbstractController
{
    #[Route('/sicherheit/passkey/name', name: 'admin_security_passkey_name', methods: ['POST'])]
    public function setName(Request $request, #[CurrentUser] ?User $user): Response
    {
        if (!$user instanceof User) {
            return new JsonResponse(['error' => 'unauthorized'], Response::HTTP_UNAUTHORIZED);
        }

        $request->getSession()->set(WebauthnCredentialNamer::SESSION_KEY, (string) $request->getPayload()->get('name'));

        return new JsonResponse(['status' => 'ok']);
    }

    #[Route('/sicherheit/passkey/{id}/delete', name: 'admin_security_passkey_delete', methods: ['POST'])]
    public function delete(
        string $id,
        Request $request,
        #[CurrentUser]
        ?User $user,
        #[Autowire(service: 'contao.repository.webauthn_credential')]
        WebauthnCredentialRepository $credentials,
        WebauthnUserEntityRepository $userEntities,
    ): Response {
        if (!$user instanceof User) {
            throw $this->createAccessDeniedException();
        }

        if (!$this->isCsrfTokenValid('submit', (string) $request->request->get('_token'))) {
            throw $this->createAccessDeniedException();
        }

        $credential = $credentials->find($id);
        if (!$credential instanceof WebauthnCredential || $credential->userHandle !== $user->getUuid()->toRfc4122()) {
            throw $this->createNotFoundException();
        }

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

        // Lockout guard: removing the last passkey is only safe if the user
        // still has a password OR magic-link sign-in is available. Treat
        // disabled magic links (per-user or globally) as "no fallback there".
        $hasPassword = null !== $user->getPassword();
        // We could also factor in global config here, but keeping the check
        // strict (password presence) avoids configuration coupling and is
        // the safest default: if you want to remove your last passkey, set
        // a password first.
        if (\count($all) <= 1 && !$hasPassword) {
            $this->addFlash('error', 'Der letzte Passkey lässt sich nicht entfernen, solange Sie kein Passwort gesetzt haben.');
        } else {
            $credentials->remove($credential);
            $this->addFlash('success', 'Passkey entfernt.');
        }

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