<?php

declare(strict_types=1);

namespace Gls\GlsPoland\AdePlus;

use Gls\GlsPoland\AdePlus\Authorization\AuthorizingCaller;
use Gls\GlsPoland\AdePlus\Fault\FaultConverterInterface;
use Gls\GlsPoland\AdePlus\Fault\FaultConvertingCaller;
use Gls\GlsPoland\Configuration\Api\ConfigurationInterface as ApiConfigurationInterface;
use Gls\GlsPoland\Soap\Client\Caller\CallerInterface;
use Gls\GlsPoland\Soap\Client\Caller\EngineCaller;
use Gls\GlsPoland\Soap\Client\Caller\LoggingCaller;
use Gls\GlsPoland\Soap\Client\Caller\RateLimitingCaller;
use Gls\GlsPoland\Soap\Client\Soap\DefaultEngineFactory;
use Gls\GlsPoland\Soap\Engine\EngineInterface;
use Gls\GlsPoland\Soap\ExtSoapEngine\Configuration\ClassMap\ClassMapCollection;
use Gls\GlsPoland\Soap\ExtSoapEngine\ExtSoapOptions;
use Psr\Log\LoggerInterface;

final class ClientFactory implements ClientFactoryInterface
{
    private const MAX_CALLS_PER_SECOND = 20;

    private $faultConverter;
    private $testWsdlUrl;
    private $prodWsdlUrl;
    private $configuration;

    private $classmap;

    public function __construct(FaultConverterInterface $faultConverter, string $testWsdlUrl, string $prodWsdlUrl, ApiConfigurationInterface $configuration)
    {
        $this->faultConverter = $faultConverter;
        $this->testWsdlUrl = $testWsdlUrl;
        $this->prodWsdlUrl = $prodWsdlUrl;
        $this->configuration = $configuration;
    }

    public function create(bool $sandbox = false, ?UserCredentialsInterface $userCredentials = null, ?LoggerInterface $logger = null): Client
    {
        $caller = $this->createCaller($sandbox, $userCredentials, $logger);

        return new Client($caller, $sandbox);
    }

    private function createCaller(bool $sandbox, ?UserCredentialsInterface $userCredentials, ?LoggerInterface $logger): CallerInterface
    {
        $engine = $this->createEngine($sandbox);
        $caller = new EngineCaller($engine);
        $caller = new FaultConvertingCaller($caller, $this->faultConverter);
        $caller = new RateLimitingCaller($caller, self::MAX_CALLS_PER_SECOND);

        if (null !== $userCredentials) {
            $caller = new AuthorizingCaller($caller, $userCredentials, $this->resolveIntegratorId());
        }

        if (null !== $logger) {
            $caller = new LoggingCaller($caller, $logger);
        }

        return $caller;
    }

    private function createEngine(bool $sandbox): EngineInterface
    {
        $wsdl = $sandbox ? $this->testWsdlUrl : $this->prodWsdlUrl;
        $options = ExtSoapOptions::defaults($wsdl)->setClassMap($this->getClassMap());

        return DefaultEngineFactory::create($options);
    }

    private function getClassMap(): ClassMapCollection
    {
        return $this->classmap ?? ($this->classmap = ClassMapFactory::getCollection());
    }

    private function resolveIntegratorId(): ?string
    {
        $integratorId = $this->configuration->getIntegratorId();

        if (null === $integratorId) {
            return null;
        }

        $normalized = trim($integratorId);

        return '' === $normalized ? null : $normalized;
    }
}
