<?php

declare (strict_types=1);
namespace GuardDog\Vendor\Webauthn;

use GuardDog\Vendor\Cose\Algorithm\Manager;
use GuardDog\Vendor\Psr\EventDispatcher\EventDispatcherInterface;
use GuardDog\Vendor\Psr\Http\Message\ServerRequestInterface;
use GuardDog\Vendor\Psr\Log\LoggerInterface;
use GuardDog\Vendor\Psr\Log\NullLogger;
use Throwable;
use GuardDog\Vendor\Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
use GuardDog\Vendor\Webauthn\CeremonyStep\CeremonyStepManager;
use GuardDog\Vendor\Webauthn\CeremonyStep\CeremonyStepManagerFactory;
use GuardDog\Vendor\Webauthn\Counter\CounterChecker;
use GuardDog\Vendor\Webauthn\Event\AuthenticatorAssertionResponseValidationFailedEvent;
use GuardDog\Vendor\Webauthn\Event\AuthenticatorAssertionResponseValidationSucceededEvent;
use GuardDog\Vendor\Webauthn\Event\CanDispatchEvents;
use GuardDog\Vendor\Webauthn\Event\NullEventDispatcher;
use GuardDog\Vendor\Webauthn\Exception\AuthenticatorResponseVerificationException;
use GuardDog\Vendor\Webauthn\MetadataService\CanLogData;
use GuardDog\Vendor\Webauthn\TokenBinding\TokenBindingHandler;
use function is_string;
use function sprintf;
class AuthenticatorAssertionResponseValidator implements CanLogData, CanDispatchEvents
{
    private LoggerInterface $logger;
    private readonly CeremonyStepManagerFactory $ceremonyStepManagerFactory;
    private EventDispatcherInterface $eventDispatcher;
    public function __construct(private readonly null|PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository = null, private readonly null|TokenBindingHandler $tokenBindingHandler = null, null|ExtensionOutputCheckerHandler $extensionOutputCheckerHandler = null, null|Manager $algorithmManager = null, null|EventDispatcherInterface $eventDispatcher = null, private null|CeremonyStepManager $ceremonyStepManager = null)
    {
        if ($this->publicKeyCredentialSourceRepository !== null) {
            trigger_deprecation('web-auth/webauthn-lib', '4.6.0', 'The parameter "$publicKeyCredentialSourceRepository" is deprecated since 4.6.0 and will be removed in 5.0.0. Please set "null" instead.');
        }
        if ($this->tokenBindingHandler !== null) {
            trigger_deprecation('web-auth/webauthn-lib', '4.3.0', 'The parameter "$tokenBindingHandler" is deprecated since 4.3.0 and will be removed in 5.0.0. Please set "null" instead.');
        }
        if ($extensionOutputCheckerHandler !== null) {
            trigger_deprecation('web-auth/webauthn-lib', '4.8.0', 'The parameter "$extensionOutputCheckerHandler" is deprecated since 4.8.0 and will be removed in 5.0.0. Please set "null" instead and inject a CheckExtensions object into the CeremonyStepManager.');
        }
        if ($algorithmManager !== null) {
            trigger_deprecation('web-auth/webauthn-lib', '4.8.0', 'The parameter "$algorithmManager" is deprecated since 4.8.0 and will be removed in 5.0.0. Please set "null" instead and inject a CheckSignature object into the CeremonyStepManager.');
        }
        $this->eventDispatcher = $eventDispatcher ?? new NullEventDispatcher();
        if ($eventDispatcher !== null) {
            trigger_deprecation('web-auth/webauthn-lib', '4.5.0', 'The parameter "$eventDispatcher" is deprecated since 4.5.0 will be removed in 5.0.0. Please use `setEventDispatcher` instead.');
        }
        if ($this->ceremonyStepManager === null) {
            trigger_deprecation('web-auth/webauthn-lib', '4.8.0', 'The parameter "$ceremonyStepManager" will mandatory in 5.0.0. Please set a CeremonyStepManager object instead and set null for $algorithmManager and $extensionOutputCheckerHandler.');
        }
        $this->logger = new NullLogger();
        $this->ceremonyStepManagerFactory = new CeremonyStepManagerFactory();
        if ($extensionOutputCheckerHandler !== null) {
            $this->ceremonyStepManagerFactory->setExtensionOutputCheckerHandler($extensionOutputCheckerHandler);
        }
        if ($algorithmManager !== null) {
            $this->ceremonyStepManagerFactory->setAlgorithmManager($algorithmManager);
        }
    }
    public static function create(null|PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository = null, null|TokenBindingHandler $tokenBindingHandler = null, null|ExtensionOutputCheckerHandler $extensionOutputCheckerHandler = null, null|Manager $algorithmManager = null, null|EventDispatcherInterface $eventDispatcher = null, null|CeremonyStepManager $ceremonyStepManager = null): self
    {
        return new self($publicKeyCredentialSourceRepository, $tokenBindingHandler, $extensionOutputCheckerHandler, $algorithmManager, $eventDispatcher, $ceremonyStepManager);
    }
    /**
     * @param string[] $securedRelyingPartyId
     *
     * @see https://www.w3.org/TR/webauthn/#verifying-assertion
     */
    public function check(string|PublicKeyCredentialSource $credentialId, AuthenticatorAssertionResponse $authenticatorAssertionResponse, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ServerRequestInterface|string $request, ?string $userHandle, null|array $securedRelyingPartyId = null): PublicKeyCredentialSource
    {
        if ($request instanceof ServerRequestInterface) {
            trigger_deprecation('web-auth/webauthn-lib', '4.5.0', sprintf('Passing a %s to the method `check` of the class "%s" is deprecated since 4.5.0 and will be removed in 5.0.0. Please inject the host as a string instead.', ServerRequestInterface::class, self::class));
        }
        if (is_string($credentialId)) {
            trigger_deprecation('web-auth/webauthn-lib', '4.6.0', sprintf('Passing a string as first to the method `check` of the class "%s" is deprecated since 4.6.0. Please inject a %s object instead.', self::class, PublicKeyCredentialSource::class));
        }
        if ($securedRelyingPartyId !== null) {
            trigger_deprecation('web-auth/webauthn-lib', '4.8.0', sprintf('Passing a list or secured relying party IDs to the method `check` of the class "%s" is deprecated since 4.8.0 and will be removed in 5.0.0. Please inject a CheckOrigin into the CeremonyStepManager instead.', self::class));
        }
        if ($credentialId instanceof PublicKeyCredentialSource) {
            $publicKeyCredentialSource = $credentialId;
        } else {
            $this->publicKeyCredentialSourceRepository instanceof PublicKeyCredentialSourceRepository || throw AuthenticatorResponseVerificationException::create('Please pass the Public Key Credential Source to the method "check".');
            $publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($credentialId);
        }
        $publicKeyCredentialSource !== null || throw AuthenticatorResponseVerificationException::create('The credential ID is invalid.');
        $host = is_string($request) ? $request : $request->getUri()->getHost();
        if ($this->ceremonyStepManager === null) {
            $this->ceremonyStepManager = $this->ceremonyStepManagerFactory->requestCeremony($securedRelyingPartyId);
        }
        try {
            $this->logger->info('Checking the authenticator assertion response', ['credentialId' => $credentialId, 'publicKeyCredentialSource' => $publicKeyCredentialSource, 'authenticatorAssertionResponse' => $authenticatorAssertionResponse, 'publicKeyCredentialRequestOptions' => $publicKeyCredentialRequestOptions, 'host' => $host, 'userHandle' => $userHandle]);
            $this->ceremonyStepManager->process($publicKeyCredentialSource, $authenticatorAssertionResponse, $publicKeyCredentialRequestOptions, $userHandle, $host);
            $publicKeyCredentialSource->counter = $authenticatorAssertionResponse->authenticatorData->signCount;
            //26.1.
            $publicKeyCredentialSource->backupEligible = $authenticatorAssertionResponse->authenticatorData->isBackupEligible();
            //26.2.
            $publicKeyCredentialSource->backupStatus = $authenticatorAssertionResponse->authenticatorData->isBackedUp();
            //26.2.
            if ($publicKeyCredentialSource->uvInitialized === \false) {
                $publicKeyCredentialSource->uvInitialized = $authenticatorAssertionResponse->authenticatorData->isUserVerified();
                //26.3.
            }
            /*
             * 26.3.
             * OPTIONALLY, if response.attestationObject is present, update credentialRecord.attestationObject to the value of response.attestationObject and update credentialRecord.attestationClientDataJSON to the value of response.clientDataJSON.
             */
            if (is_string($credentialId) && $this->publicKeyCredentialSourceRepository instanceof PublicKeyCredentialSourceRepository) {
                $this->publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource);
            }
            //All good. We can continue.
            $this->logger->info('The assertion is valid');
            $this->logger->debug('Public Key Credential Source', ['publicKeyCredentialSource' => $publicKeyCredentialSource]);
            $this->eventDispatcher->dispatch($this->createAuthenticatorAssertionResponseValidationSucceededEvent(null, $authenticatorAssertionResponse, $publicKeyCredentialRequestOptions, $host, $userHandle, $publicKeyCredentialSource));
            // 27.
            return $publicKeyCredentialSource;
        } catch (AuthenticatorResponseVerificationException $throwable) {
            $this->logger->error('An error occurred', ['exception' => $throwable]);
            $this->eventDispatcher->dispatch($this->createAuthenticatorAssertionResponseValidationFailedEvent($publicKeyCredentialSource, $authenticatorAssertionResponse, $publicKeyCredentialRequestOptions, $host, $userHandle, $throwable));
            throw $throwable;
        }
    }
    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
    {
        $this->eventDispatcher = $eventDispatcher;
    }
    /**
     * @deprecated since 4.8.0 and will be removed in 5.0.0. Please inject a CheckCounter object into a CeremonyStepManager instead.
     */
    public function setCounterChecker(CounterChecker $counterChecker): self
    {
        $this->ceremonyStepManagerFactory->setCounterChecker($counterChecker);
        return $this;
    }
    protected function createAuthenticatorAssertionResponseValidationSucceededEvent(null|string $credentialId, AuthenticatorAssertionResponse $authenticatorAssertionResponse, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ServerRequestInterface|string $host, ?string $userHandle, PublicKeyCredentialSource $publicKeyCredentialSource): AuthenticatorAssertionResponseValidationSucceededEvent
    {
        if ($host instanceof ServerRequestInterface) {
            trigger_deprecation('web-auth/webauthn-lib', '4.5.0', sprintf('Passing a %s to the method `createAuthenticatorAssertionResponseValidationSucceededEvent` of the class "%s" is deprecated since 4.5.0 and will be removed in 5.0.0. Please inject the host as a string instead.', ServerRequestInterface::class, self::class));
        }
        return new AuthenticatorAssertionResponseValidationSucceededEvent($credentialId, $authenticatorAssertionResponse, $publicKeyCredentialRequestOptions, $host, $userHandle, $publicKeyCredentialSource);
    }
    protected function createAuthenticatorAssertionResponseValidationFailedEvent(string|PublicKeyCredentialSource $publicKeyCredentialSource, AuthenticatorAssertionResponse $authenticatorAssertionResponse, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ServerRequestInterface|string $host, ?string $userHandle, Throwable $throwable): AuthenticatorAssertionResponseValidationFailedEvent
    {
        if ($host instanceof ServerRequestInterface) {
            trigger_deprecation('web-auth/webauthn-lib', '4.5.0', sprintf('Passing a %s to the method `createAuthenticatorAssertionResponseValidationFailedEvent` of the class "%s" is deprecated since 4.5.0 and will be removed in 5.0.0. Please inject the host as a string instead.', ServerRequestInterface::class, self::class));
        }
        return new AuthenticatorAssertionResponseValidationFailedEvent($publicKeyCredentialSource, $authenticatorAssertionResponse, $publicKeyCredentialRequestOptions, $host, $userHandle, $throwable);
    }
}
