<?php

namespace Drupal\miniorange_webauthn\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\miniorange_webauthn\MoDto\MoPubKeyCredAssertionRespDto;

use Drupal\miniorange_webauthn\MoProtector\MoHandler\MoFailureHandlerInterface;
use Drupal\miniorange_webauthn\MoRepo\MoPubKeyCredSourceRepoInterface;
use Drupal\user\Entity\User;
use ParagonIE\ConstantTime\Base64UrlSafe;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Webauthn\AuthenticatorAssertionResponse;
use Webauthn\AuthenticatorAssertionResponseValidator;
use Webauthn\CeremonyStep\CeremonyStepManager;
use Webauthn\CeremonyStep\CheckChallenge;
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialUserEntity;

/**
 *
 */
class MoAssertionRespController extends ControllerBase {

  /**
   *
   */
  public function __construct(
    PrivateTempStoreFactory $tempStorage,
    private readonly MoPubKeyCredSourceRepoInterface $pubKeyCredSourceRepo,
    private readonly MoFailureHandlerInterface $moFailureHandler
  ) {
    $this->tempStore = $tempStorage->get('miniorange_webauthn');
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('tempstore.private'),
      $container->get('mo_webauthn.pub_key_cred_source_repo'),
      $container->get('mo_webauthn.failure_handler')
    );
  }

  /**
   * @throws \Webauthn\Exception\InvalidDataException
   * @throws \JsonException
   */
  public function __invoke(Request $request) {
    try {
      $format = $request->getContentTypeFormat();
      $format === 'json' || throw new BadRequestHttpException('Only JSON content type allowed');
      $content = json_decode($request->getContent(), TRUE);
      $publicKeyCredential = MoPubKeyCredAssertionRespDto::build(...$content);
      $response = $publicKeyCredential->response;
      $response instanceof AuthenticatorAssertionResponse || throw new BadRequestHttpException(
        'Invalid response'
      );
      /** @var \Drupal\miniorange_webauthn\Entity\MoWebauthnCredential $publicKeyData */
      $publicKeyCredSource = $this->pubKeyCredSourceRepo->getPublicKeyCredentialSource(
        base64_encode(Base64UrlSafe::decode($publicKeyCredential->rawId))
      );

      $storedData = $this->tempStore->get('mo_webauthn_assertion_req_opt');

      $pubKeyCredReqOpt = $storedData->getPublicKeyCredentialOptions();
      $pubKeyCredReqOpt instanceof PublicKeyCredentialRequestOptions || throw new BadRequestHttpException(
        'Unable to find the public key credential creation options'
      );
      $pubKeyCredReqOpt->challenge = Base64UrlSafe::decode($pubKeyCredReqOpt->challenge);
      $storedUser = $storedData->getPublicKeyCredentialUserEntity();
      $storedUser instanceof PublicKeyCredentialUserEntity || throw new BadRequestHttpException(
        'Unable to find the public key credential user entity'
      );

      $attestationValidator = new AuthenticatorAssertionResponseValidator(
        new CeremonyStepManager(
          [
            new CheckChallenge(),
          ]
        )
      );
      $publicKeyCredSource = $attestationValidator->check(
        $publicKeyCredSource, $response, $pubKeyCredReqOpt, \Drupal::request()->getHost(), $storedUser->id
      );

      $account = User::load(Base64UrlSafe::decode($publicKeyCredSource->userHandle));
      \Drupal::logger('miniorange_webauthn')->notice(json_encode($account));

      if ($account && $account->isActive()) {
        user_login_finalize($account);
        \Drupal::service('miniorange_2fa.logger')->logEvent($account, 'login_attempt', 'success', 'User logged in successfully using WebAuthn.');
        return new JsonResponse([
          'success' => TRUE,
          'uid' => $account->id(),
          'message' => 'User logged in successfully.',
        ]);
      }
      else {
        \Drupal::service('miniorange_2fa.logger')->logEvent($account, 'login_attempt', 'failure', 'Invalid credentials. WebAuthn verification failed.');
        return new JsonResponse([
          'success' => FALSE,
          'error' => 'Invalid credentials.',
          'message' => 'WebAuthn verification failed.',
        ], 403);
      }
    }
    catch (\Throwable $throwable) {
      return $this->moFailureHandler->onAuthenticationFailure($request, $throwable);
    }
  }

}
