<?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\PrestaShop\CommandBus;

use League\Tactician\CommandBus;
use League\Tactician\Handler\CommandHandlerMiddleware;
use League\Tactician\Handler\CommandNameExtractor\ClassNameExtractor;
use League\Tactician\Handler\Locator\CallableLocator;
use League\Tactician\Handler\MethodNameInflector\HandleInflector;
use League\Tactician\Middleware;
use PrestaShop\PrestaShop\Core\CommandBus\CommandBusInterface;
use PrestaShop\PrestaShop\Core\CommandBus\TacticianCommandBusAdapter;
use PrestaShopBundle\CommandBus\MessengerCommandBus;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\Messenger\Handler\HandlerDescriptor;
use Symfony\Component\Messenger\Handler\HandlersLocator;
use Symfony\Component\Messenger\MessageBus;
use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware;
use Symfony\Contracts\Service\ServiceProviderInterface;

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

final class CommandBusFactory
{
    /**
     * @param ContainerInterface $container locator of command handlers by command class name
     */
    public static function create(ContainerInterface $container): CommandBusInterface
    {
        if (class_exists(TacticianCommandBusAdapter::class)) {
            return self::createTacticianCommandBus($container);
        }

        if (class_exists(MessengerCommandBus::class) && $container instanceof ServiceProviderInterface) {
            return self::createMessengerCommandBus($container);
        }

        return self::createBasicCommandBus($container);
    }

    /**
     * @param ContainerInterface $container locator of command handlers by command class name
     * @param iterable<Middleware> $middlewares
     */
    public static function createTacticianCommandBus(ContainerInterface $container, iterable $middlewares = []): CommandBusInterface
    {
        if (!class_exists(TacticianCommandBusAdapter::class)) {
            throw new \LogicException('Cannot create Tactician command bus because the PS adapter class does not exist.');
        }

        if (!is_array($middlewares)) {
            $middlewares = iterator_to_array($middlewares);
        }

        $middlewares[] = new CommandHandlerMiddleware(
            new ClassNameExtractor(),
            new CallableLocator([$container, 'get']),
            new HandleInflector()
        );

        return new TacticianCommandBusAdapter(new CommandBus($middlewares));
    }

    public static function createMessengerCommandBus(ServiceProviderInterface $container, iterable $middlewares = []): CommandBusInterface
    {
        if (!class_exists(MessengerCommandBus::class)) {
            throw new \LogicException('Cannot create Messenger command bus because the PS adapter class does not exist.');
        }

        if (!is_array($middlewares)) {
            $middlewares = iterator_to_array($middlewares);
        }

        $serviceIds = array_keys($container->getProvidedServices());

        /** @var array<string, iterable<HandlerDescriptor>> $handlers */
        $handlers = array_map(static function (string $id) use ($container) {
            return new RewindableGenerator(static function () use ($container, $id) {
                yield new HandlerDescriptor($container->get($id));
            }, 1);
        }, array_combine($serviceIds, $serviceIds));

        $middlewares[] = new HandleMessageMiddleware(new HandlersLocator($handlers));

        return new MessengerCommandBus(new MessageBus($middlewares));
    }

    private static function createBasicCommandBus(ContainerInterface $container): CommandBusInterface
    {
        return new class($container) implements CommandBusInterface {
            /**
             * @var ContainerInterface
             */
            private $container;

            public function __construct(ContainerInterface $container)
            {
                $this->container = $container;
            }

            public function handle($command)
            {
                $handler = $this->container->get(get_class($command));

                return $handler($command);
            }
        };
    }
}
