<?php declare(strict_types=1);
/**
 * This file is part of the SplendidBear Websites' projects.
 *
 * Copyright (c) 2026 @ www.splendidbear.org
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace App\Controller;

use App\Entity\User;
use App\Security\PasskeyToken;
use App\Service\WebAuthnService;
use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialParameters;
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialUserEntity;
use function random_bytes;

/**
 * Class WebAuthnController
 *
 * @package  App\Controller
 * @author   Lang <https://www.splendidbear.org>
 * @category Class
 * @license  https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
 * @link     www.splendidbear.org
 * @since    2026. 04. 12.
 */
#[AsController]
#[Route('/api/webauthn', name: 'api_webauthn_')]
class WebAuthnController extends AbstractController
{
    public function __construct(
        private readonly WebAuthnService       $webAuthnService,
        private readonly TokenStorageInterface $tokenStorage,
    ) {
    }

    #[Route('/registration/begin', name: 'registration_begin', methods: ['POST'])]
    public function beginRegistration(Request $request): JsonResponse
    {
        /** @var User $user */
        $user = $this->getUser();
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        try {
            $data = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
            $credentialName = $data['credentialName'] ?? 'My Passkey';

            $rpEntity = new PublicKeyCredentialRpEntity(
                'Mine Seeker',
                $_SERVER['HTTP_HOST'] ?? 'localhost',
            );

            $userEntity = new PublicKeyCredentialUserEntity(
                $user->getUserIdentifier(),
                (string)$user->id,
                $user->getUsername(),
            );

            $credentialParameters = [
                PublicKeyCredentialParameters::create('public-key', -7),   // ES256
                PublicKeyCredentialParameters::create('public-key', -257), // RS256
            ];

            $authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create();

            $creationOptions = PublicKeyCredentialCreationOptions::create(
                $rpEntity,
                $userEntity,
                random_bytes(32),
                $credentialParameters,
                $authenticatorSelectionCriteria,
                PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT
            );

            $request->getSession()->set('webauthn_creation_options', $creationOptions);
            $request->getSession()->set('webauthn_credential_name', $credentialName);

            /** Convert to JSON-serializable array */
            $response = [
                'challenge'          => base64_encode($creationOptions->challenge),
                'rp'                 => [
                    'name' => $creationOptions->rp->name,
                    'id'   => $creationOptions->rp->id,
                ],
                'user'               => [
                    'id'          => base64_encode($creationOptions->user->id),
                    'name'        => $creationOptions->user->name,
                    'displayName' => $creationOptions->user->displayName,
                ],
                'pubKeyCredParams'   => array_map(fn($param) => [
                    'type' => $param->type,
                    'alg'  => $param->alg,
                ], $creationOptions->pubKeyCredParams),
                'timeout'            => $creationOptions->timeout,
                'attestation'        => $creationOptions->attestation,
                'excludeCredentials' => array_map(fn($cred) => [
                    'id'         => base64_encode($cred->id),
                    'type'       => $cred->type,
                    'transports' => $cred->transports,
                ], $creationOptions->excludeCredentials),
            ];

            return new JsonResponse($response);
        } catch (Exception $e) {
            return new JsonResponse(
                ['error' => $e->getMessage()],
                Response::HTTP_BAD_REQUEST
            );
        }
    }

    #[Route('/registration/complete', name: 'registration_complete', methods: ['POST'])]
    public function completeRegistration(Request $request): JsonResponse
    {
        /** @var User $user */
        $user = $this->getUser();
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        try {
            $data = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
            $credentialName = $request->getSession()->get('webauthn_credential_name', 'My Passkey');

            $credentialJson = $data['credential'] ?? null;
            if ($credentialJson === null) {
                return new JsonResponse(
                    ['error' => 'No credential provided'],
                    Response::HTTP_BAD_REQUEST
                );
            }

            /** Store the credential with user ID for later retrieval during authentication */
            $credentialJson['userId'] = $user->id;
            $credentialJson['username'] = $user->getUsername();

            /** Save the credential data directly */
            $this->webAuthnService->saveCredential(
                $user,
                $credentialJson,
                $credentialName
            );

            $request->getSession()->remove('webauthn_creation_options');
            $request->getSession()->remove('webauthn_credential_name');

            return new JsonResponse(['success' => true]);
        } catch (Exception $e) {
            return new JsonResponse(
                ['error' => 'Registration failed: ' . $e->getMessage()],
                Response::HTTP_BAD_REQUEST
            );
        }
    }

    #[Route('/credentials', name: 'credentials', methods: ['GET'])]
    public function getCredentials(): JsonResponse
    {
        /** @var User $user */
        $user = $this->getUser();
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        $credentials = $this->webAuthnService->getCredentialsForUser($user);

        return new JsonResponse(array_map(fn($credential) => [
            'id'                    => $credential->id,
            'name'                  => $credential->credentialName,
            'createdAt'             => $credential->createdAt?->format('Y-m-d H:i:s'),
            'lastUsedAt'            => $credential->lastUsedAt?->format('Y-m-d H:i:s'),
            'isBackupEligible'      => $credential->isBackupEligible,
            'isBackupAuthenticated' => $credential->isBackupAuthenticated,
        ], $credentials));
    }

    #[Route('/credentials/{id}', name: 'credential_delete', methods: ['DELETE'])]
    public function deleteCredential(int $id): JsonResponse
    {
        /** @var User $user */
        $user = $this->getUser();
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        if ($this->webAuthnService->deleteCredential($id, $user)) {
            return new JsonResponse(['success' => true]);
        }

        return new JsonResponse(['error' => 'Credential not found'], Response::HTTP_NOT_FOUND);
    }

    #[Route('/credentials/{id}/rename', name: 'credential_rename', methods: ['PATCH'])]
    public function renameCredential(int $id, Request $request): JsonResponse
    {
        /** @var User $user */
        $user = $this->getUser();
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

        try {
            $data = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
            $name = $data['name'] ?? '';

            if (empty($name)) {
                return new JsonResponse(
                    ['error' => 'Name is required'],
                    Response::HTTP_BAD_REQUEST
                );
            }

            if ($this->webAuthnService->renameCredential($id, $user, $name)) {
                return new JsonResponse(['success' => true]);
            }

            return new JsonResponse(['error' => 'Credential not found'], Response::HTTP_NOT_FOUND);
        } catch (Exception $e) {
            return new JsonResponse(
                ['error' => $e->getMessage()],
                Response::HTTP_BAD_REQUEST
            );
        }
    }

    #[Route('/authentication/begin', name: 'authentication_begin', methods: ['POST'])]
    public function beginAuthentication(Request $request): JsonResponse
    {
        try {
            /** Generate challenge */
            $challenge = random_bytes(32);

            /** Store in session for verification later */
            $request->getSession()->set('webauthn_request_challenge', $challenge);

            /**
             * Return simple JSON response - no credentials needed for initial request
             * Client will handle the credential filtering
             */
            $response = [
                'challenge'        => base64_encode($challenge),
                'timeout'          => 60000,
                'rpId'             => $_SERVER['HTTP_HOST'] ?? 'localhost',
                'userVerification' => 'preferred',
                'allowCredentials' => [],
            ];

            return new JsonResponse($response);
        } catch (Exception $e) {
            return new JsonResponse(
                ['error' => $e->getMessage()],
                Response::HTTP_BAD_REQUEST
            );
        }
    }

    #[Route('/authentication/complete', name: 'authentication_complete', methods: ['POST'])]
    public function completeAuthentication(Request $request): JsonResponse
    {
        try {
            $data = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
            $credential = $data['credential'] ?? null;

            if (!$credential) {
                return new JsonResponse(
                    ['error' => 'No credential provided'],
                    Response::HTTP_BAD_REQUEST
                );
            }

            /** The credential ID tells us which user registered this passkey */
            $credentialId = $credential['id'] ?? null;
            if (!$credentialId) {
                return new JsonResponse(
                    ['error' => 'Invalid credential'],
                    Response::HTTP_BAD_REQUEST
                );
            }

            /** Find the user who owns this credential */
            $user = $this->webAuthnService->findUserByCredentialId($credentialId);

            if (!$user) {
                return new JsonResponse(
                    ['error' => 'Credential not found'],
                    Response::HTTP_NOT_FOUND
                );
            }

            /** Update last used timestamp */
            $this->webAuthnService->updateLastUsedAt($credentialId, $user);

            /** Log in the user using token storage */
            $token = new PasskeyToken($user, 'main', $user->getRoles());
            $this->tokenStorage->setToken($token);
            $request->getSession()->set('_security_main', serialize($token));

            return new JsonResponse([
                'success'  => true,
                'redirect' => '/',
                'message'  => 'Successfully authenticated with passkey',
            ]);
        } catch (Exception $e) {
            return new JsonResponse(
                ['error' => $e->getMessage()],
                Response::HTTP_BAD_REQUEST
            );
        }
    }
}
