<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\Api;

use InPost\International\Configuration\EnvironmentConfiguration;
use InPost\International\Configuration\Repository\ApiConfigurationRepository;
use InPost\International\Configuration\Repository\ApiConfigurationRepositoryInterface;
use InPost\International\Http\Client\AuthorizingClient;
use InPost\International\Http\Client\Factory\ClientFactoryInterface;
use InPost\International\Http\Client\LoggingClient;
use InPost\International\OAuth2\Authentication\InMemoryCredentialsRepository;
use InPost\International\OAuth2\AuthorizationProviderFactoryInterface;
use InPost\International\OAuth2\AuthorizationProviderInterface;
use InPost\International\OAuth2\LazyAuthorizationProvider;
use InPost\International\OAuth2\Token\AccessTokenRepositoryInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Serializer\SerializerInterface;

if (!defined('_PS_VERSION_')) {
    exit;
}

final class ApiClientFactory
{
    use LoggerAwareTrait;

    /**
     * @var ApiConfigurationRepository
     */
    private $configurationRepository;

    /**
     * @var RequestFactoryInterface
     */
    private $requestFactory;

    /**
     * @var StreamFactoryInterface
     */
    private $streamFactory;

    /**
     * @var SerializerInterface
     */
    private $serializer;

    /**
     * @var ClientFactoryInterface
     */
    private $clientFactory;

    /**
     * @var AuthorizationProviderFactoryInterface
     */
    private $authorizationProviderFactory;

    /**
     * @var array<string, mixed>
     */
    private $loggingOptions = [];

    /**
     * @param ApiConfigurationRepository $configurationRepository
     */
    public function __construct(ApiConfigurationRepositoryInterface $configurationRepository, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory, SerializerInterface $serializer, ClientFactoryInterface $clientFactory, AuthorizationProviderFactoryInterface $authorizationProviderFactory = null)
    {
        $this->configurationRepository = $configurationRepository;
        $this->requestFactory = $requestFactory;
        $this->streamFactory = $streamFactory;
        $this->serializer = $serializer;
        $this->clientFactory = $clientFactory;
        $this->authorizationProviderFactory = $authorizationProviderFactory ?? new AuthorizationProviderFactory($requestFactory, $streamFactory, $clientFactory);
    }

    /**
     * @param array<string, mixed> $options {@see LoggingClient} options
     */
    public function setLoggingOptions(array $options): void
    {
        $this->loggingOptions = $options;
    }

    public function create(string $env): ApiClientInterface
    {
        $configuration = $this->configurationRepository->getConfiguration($env);
        $tokenRepository = $this->configurationRepository->specializeTokenRepository($env);
        $authProvider = $this->createAuthorizationProvider($configuration, $tokenRepository);
        $httpClient = $this->createHttpClient($authProvider);

        return new ApiClient($httpClient, $this->requestFactory, $this->streamFactory, $this->serializer, $configuration->getEnvironment());
    }

    private function createHttpClient(AuthorizationProviderInterface $authProvider): ClientInterface
    {
        $client = $this->clientFactory->create();
        $client = new AuthorizingClient($client, $authProvider);

        if (null !== $this->logger) {
            $client = new LoggingClient($client, $this->logger, $this->loggingOptions);
        }

        return $client;
    }

    private function createAuthorizationProvider(EnvironmentConfiguration $configuration, AccessTokenRepositoryInterface $tokenRepository = null): AuthorizationProviderInterface
    {
        if (null === $credentials = $configuration->getClientCredentials()) {
            throw new \RuntimeException('Client credentials are not configured.');
        }

        return new LazyAuthorizationProvider(
            $this->authorizationProviderFactory,
            $configuration->getEnvironment()->getOAuth2UriCollection(),
            new InMemoryCredentialsRepository($credentials),
            $tokenRepository
        );
    }
}
