<?php

namespace Velis\Command;

use ArrayObject;
use League\Tactician\Handler\Locator\HandlerLocator as HandlerLocatorInterface;
use Psr\SimpleCache\InvalidArgumentException;
use ReflectionClass;
use ReflectionException;
use RuntimeException;
use Velis\App;
use Velis\Exception;

/**
 * Command handler locator
 * @author Olek Procki <olo@velis.pl>
 */
class HandlerLocator implements HandlerLocatorInterface
{
    /**
     * Returns handler instance for given $commandName
     *
     * @param string $commandName
     * @return object
     * @throws InvalidArgumentException
     * @throws ReflectionException
     */
    public function getHandlerForCommand($commandName)
    {
        $handlerClass = $this->resolveHandlerClass($commandName);
        $arguments = $this->resolveConstructorArguments($handlerClass);

        return new $handlerClass(...$arguments);
    }

    /**
     * @param string $commandName
     * @return string
     */
    private function resolveHandlerClass(string $commandName): string
    {
        $handlerClass = preg_replace(
            ['/Command\\\\/', '/Command$/'],
            ['Command\Handler\\', 'Handler'],
            $commandName
        );

        if (!$handlerClass || !class_exists($handlerClass)) {
            throw new RuntimeException('Could not find handler class for command: ' . $commandName);
        }

        return $handlerClass;
    }

    /**
     * @param string $handlerClass
     * @return array
     * @throws Exception
     * @throws InvalidArgumentException
     * @throws ReflectionException
     */
    private function resolveConstructorArguments(string $handlerClass): array
    {
        $reflectionClass = new ReflectionClass($handlerClass);
        $constructor = $reflectionClass->getConstructor();
        if (null === $constructor) {
            return [];
        }

        $declaringClassReflection = $constructor->getDeclaringClass();
        if (ArrayObject::class === $declaringClassReflection->getName()) {
            if (App::$config->settings->deprecations->handlerInheritanceCheck) {
                Exception::raise(sprintf('%s: Command bus handler class cannot inherit from %s', $handlerClass, ArrayObject::class));
            } else {
                return [];
            }
        }

        $constructorParameters = $constructor->getParameters();
        $arguments = [];

        foreach ($constructorParameters as $parameter) {
            $className = $parameter->getClass()->getName();
            $arguments[] = App::$di->get($className);
        }

        return $arguments;
    }
}
