<?php

declare(strict_types=1);

namespace CpCreation\AltchaBundle\DependencyInjection;

use CpCreation\AltchaBundle\Command\SynchronizeScopesCommand;
use CpCreation\AltchaBundle\Contract\AltchaServiceClientInterface;
use CpCreation\AltchaBundle\Contract\ScopeProviderInterface;
use CpCreation\AltchaBundle\Contract\TimeProviderInterface;
use CpCreation\AltchaBundle\Controller\ChallengeController;
use CpCreation\AltchaBundle\Form\AltchaType;
use CpCreation\AltchaBundle\Form\AltchaValidationGroupsExtension;
use CpCreation\AltchaBundle\Http\AltchaServiceClient;
use CpCreation\AltchaBundle\Scope\ScopeRegistry;
use CpCreation\AltchaBundle\Security\ClientIdentity;
use CpCreation\AltchaBundle\Security\LocalRateLimiter;
use CpCreation\AltchaBundle\Security\ProtectionEnvelopeManager;
use CpCreation\AltchaBundle\Security\ProtectionState;
use CpCreation\AltchaBundle\Security\SystemTimeProvider;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\RateLimiter\Storage\CacheStorage;

final class CpCreationAltchaExtension extends Extension implements PrependExtensionInterface
{
    public function prepend(ContainerBuilder $container): void
    {
        if ($container->hasExtension('twig')) {
            $container->prependExtensionConfig('twig', [
                'form_themes' => ['@CpCreationAltcha/form/altcha_theme.html.twig'],
            ]);
        }
    }

    public function load(array $configs, ContainerBuilder $container): void
    {
        /** @var array{
         *     service_url: string,
         *     runtime_token: string,
         *     scope_sync_token: string,
         *     envelope_secret: string,
         *     subject_secret: string,
         *     envelope_ttl_seconds: int,
         *     challenge_limit_per_minute: int,
         *     submission_limit_per_minute: int,
         *     scope_source: string,
         *     scopes: array<string, string>
         * } $config
         */
        $config = $this->processConfiguration(new Configuration(), $configs);

        $container->setDefinition(SystemTimeProvider::class, new Definition(SystemTimeProvider::class));
        $container->setAlias(TimeProviderInterface::class, new Alias(SystemTimeProvider::class));
        $container->setDefinition(ClientIdentity::class, new Definition(ClientIdentity::class));
        $container->setDefinition(ProtectionState::class, new Definition(ProtectionState::class));
        $container->registerForAutoconfiguration(ScopeProviderInterface::class)
            ->addTag('cp_creation_altcha.scope_provider');
        $container->setDefinition('cp_creation_altcha.rate_limiter.storage', new Definition(CacheStorage::class, [
            new Reference('cache.app'),
        ]));
        $container->setDefinition('cp_creation_altcha.rate_limiter.challenge', new Definition(RateLimiterFactory::class, [[
            'id' => 'cp_creation_altcha_challenge',
            'policy' => 'sliding_window',
            'limit' => $config['challenge_limit_per_minute'],
            'interval' => '1 minute',
        ], new Reference('cp_creation_altcha.rate_limiter.storage'), new Reference('lock.factory', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]));
        $container->setDefinition('cp_creation_altcha.rate_limiter.submission', new Definition(RateLimiterFactory::class, [[
            'id' => 'cp_creation_altcha_submission',
            'policy' => 'sliding_window',
            'limit' => $config['submission_limit_per_minute'],
            'interval' => '1 minute',
        ], new Reference('cp_creation_altcha.rate_limiter.storage'), new Reference('lock.factory', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]));
        $container->setDefinition(LocalRateLimiter::class, new Definition(LocalRateLimiter::class, [
            new Reference('cp_creation_altcha.rate_limiter.challenge'),
            new Reference('cp_creation_altcha.rate_limiter.submission'),
        ]));
        $container->setDefinition(ScopeRegistry::class, new Definition(ScopeRegistry::class, [
            $config['scopes'],
            new TaggedIteratorArgument('cp_creation_altcha.scope_provider'),
            $config['scope_source'],
        ]));
        $container->setDefinition(ProtectionEnvelopeManager::class, new Definition(ProtectionEnvelopeManager::class, [
            $config['envelope_secret'],
            new Reference(TimeProviderInterface::class),
            $config['envelope_ttl_seconds'],
        ]));
        $container->setDefinition(AltchaServiceClient::class, new Definition(AltchaServiceClient::class, [
            new Reference('http_client.transport'),
            $config['service_url'],
            $config['runtime_token'],
            $config['scope_sync_token'],
        ]));
        $container->setAlias(AltchaServiceClientInterface::class, new Alias(AltchaServiceClient::class));

        $container->setDefinition(AltchaType::class, (new Definition(AltchaType::class, [
            new Reference(AltchaServiceClientInterface::class),
            new Reference(ProtectionEnvelopeManager::class),
            new Reference(ProtectionState::class),
            new Reference(ScopeRegistry::class),
            new Reference(LocalRateLimiter::class),
            new Reference('request_stack'),
            new Reference('translator'),
            new Reference('router'),
            $config['subject_secret'],
            new Reference(ClientIdentity::class),
        ]))->addTag('form.type'));
        $container->setDefinition(AltchaValidationGroupsExtension::class, (new Definition(
            AltchaValidationGroupsExtension::class,
            [new Reference(ProtectionState::class)],
        ))->addTag('form.type_extension'));
        $container->setDefinition(ChallengeController::class, (new Definition(ChallengeController::class, [
            new Reference(AltchaServiceClientInterface::class),
            new Reference(ScopeRegistry::class),
            new Reference(ProtectionEnvelopeManager::class),
            new Reference(LocalRateLimiter::class),
            new Reference(ClientIdentity::class),
        ]))->setPublic(true)->addTag('controller.service_arguments'));
        $container->setDefinition(SynchronizeScopesCommand::class, (new Definition(SynchronizeScopesCommand::class, [
            new Reference(AltchaServiceClientInterface::class),
            new Reference(ScopeRegistry::class),
        ]))->addTag('console.command'));
    }
}
