<?php

declare(strict_types=1);

namespace Drupal\miniorange_webauthn\MoService;

use InvalidArgumentException;

use ParagonIE\ConstantTime\Base64UrlSafe;
use Webauthn\AuthenticationExtensions\AuthenticationExtension;
use Webauthn\AuthenticationExtensions\AuthenticationExtensions;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialDescriptor;
use Webauthn\PublicKeyCredentialParameters;
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialUserEntity;


final class MoPubKeyCredCreateOptFactory
{

  public function __construct(
    private readonly MoPubKeyProfileGenFactory $moPubKeyProfileGenFactory
  )
  {
  }

  public function create(
        PublicKeyCredentialUserEntity $userEntity,
        array $excludeCredentials = [],
        null|AuthenticatorSelectionCriteria $authenticatorSelection = null,
        null|string $attestationConveyance = null,
        null|AuthenticationExtensions $AuthenticationExtensions = null
    ): PublicKeyCredentialCreationOptions {

        $profile = $this->moPubKeyProfileGenFactory->generateRuntimeProfile();
        $timeout = $profile['timeout'] ?? null;

        $attestation = $attestationConveyance ?? $profile['attestation_conveyance'] ?? null;

        $options = PublicKeyCredentialCreationOptions
            ::create(
                $this->createRpEntity($profile),
                $userEntity,
                Base64UrlSafe::encode(random_bytes($profile['challenge_length'])),
                $this->createCredentialParameters($profile),
                authenticatorSelection: $authenticatorSelection ?? $this->createAuthenticatorSelectionCriteria(
                    $profile
                ),
                attestation: $attestation,
                excludeCredentials: $excludeCredentials,
                timeout: $timeout,
                extensions: $AuthenticationExtensions ?? $this->createExtensions($profile)
            );

        return $options;
    }

    /**
     * @param mixed[] $profile
     */
    private function createExtensions(array $profile): AuthenticationExtensions
    {
        return AuthenticationExtensions::create(
            array_map(
                static fn (string $name, mixed $value): AuthenticationExtension => AuthenticationExtension::create(
                    $name,
                    $value
                ),
                array_keys($profile['extensions']),
                $profile['extensions']
            )
        );
    }

    /**
     * @param mixed[] $profile
     */
    private function createAuthenticatorSelectionCriteria(array $profile): AuthenticatorSelectionCriteria
    {
        return AuthenticatorSelectionCriteria::create(
            $profile['authenticator_selection_criteria']['authenticator_attachment'],
            $profile['authenticator_selection_criteria']['user_verification'],
            $profile['authenticator_selection_criteria']['resident_key'],
        );
    }

    /**
     * @param mixed[] $profile
     */
    private function createRpEntity(array $profile): PublicKeyCredentialRpEntity
    {
        return PublicKeyCredentialRpEntity::create($profile['rp']['name'], $profile['rp']['id']);
    }

    /**
     * @param mixed[] $profile
     *
     * @return PublicKeyCredentialParameters[]
     */
    private function createCredentialParameters(array $profile): array
    {
        $callback = static fn ($alg): PublicKeyCredentialParameters => PublicKeyCredentialParameters::create(
            PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
            $alg
        );

        return array_map($callback, $profile['public_key_credential_parameters']);
    }

}
