<?php

/*
 * This file is part of the PHPBench package
 *
 * (c) Daniel Leech <daniel@dantleech.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 */

namespace PhpBench\Tests\Unit\Registry;

use InvalidArgumentException;
use PhpBench\Json\JsonDecoder;
use PhpBench\Registry\Config;
use PhpBench\Registry\ConfigurableRegistry;
use PhpBench\Registry\RegistrableInterface;
use PhpBench\Registry\Registry;
use Prophecy\Argument;
use Seld\JsonLint\ParsingException;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ConfigurableRegistryTest extends RegistryTest
{
    /** @var ConfigurableRegistry<RegistrableInterface> */
    protected Registry $registry;

    protected function setUp(): void
    {
        parent::setUp();

        /** @var ConfigurableRegistry<RegistrableInterface> $configurableRegistry */
        $configurableRegistry = new ConfigurableRegistry(
            'test',
            $this->container->reveal(),
            new JsonDecoder(),
            'test.option',
        );
        $this->registry = $configurableRegistry;
    }

    /**
     * It should set configuration for a registered service
     * It should retrieve configurations.
     */
    public function testGetSetConfig(): void
    {
        $config = [
            'test' => 'service',
            'one' => 1,
            'two' => 2,
        ];

        $this->registry->setService('service', $this->service1->reveal());
        $this->service1->configure(Argument::type(OptionsResolver::class))->will(function ($args): void {
            $args[0]->setDefaults(['one' => 'one', 'two' => 'two']);
        });

        $this->registry->setConfig('one', $config);

        $result = $this->registry->getConfig('one');
        $this->assertEquals($config, $result->getArrayCopy());
    }

    /**
     * It should resolve configs that exend other configs.
     */
    public function testResolveExtended(): void
    {
        $config1 = [
            'test' => 'service',
            'one' => 'two',
            'three' => 'four',
        ];
        $config2 = [
            'extends' => 'config1',
            'two' => 'three',
            'four' => 'five',
        ];

        $this->registry->setService('service', $this->service1->reveal());
        $this->service1->configure(Argument::type(OptionsResolver::class))->will(function ($args): void {
            $args[0]->setDefaults([
                'test' => null,
                'one' => null,
                'two' => null,
                'three' => null,
                'four' => null,
            ]);
        });

        $this->registry->setConfig('config1', $config1);
        $this->registry->setConfig('config2', $config2);

        $result = $this->registry->getConfig('config2');
        $this->assertEquals([
            'test' => 'service',
            'one' => 'two',
            'three' => 'four',
            'two' => 'three',
            'four' => 'five',
        ], $result->getArrayCopy());
    }

    /**
     * It should throw an exception if a config extends a config from a different service.
     *
     */
    public function testExtendsDifferentServiceException(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('test configuration for service "service2" cannot extend configuration for different service "service1"');
        $config1 = [
            'test' => 'service1',
        ];
        $config2 = [
            'test' => 'service2',
            'extends' => 'config1',
        ];

        $this->registry->setService('service1', $this->service1->reveal());
        $this->registry->setService('service2', $this->service2->reveal());

        $this->registry->setConfig('config1', $config1);
        $this->registry->setConfig('config2', $config2);

        $this->registry->getConfig('config2');
    }

    /**
     * If a JSON encoded string is passed to getConfig, then it should be processed.
     */
    public function testGetConfigJsonString(): void
    {
        $this->registry->setService('service', $this->service1->reveal());
        $this->service1->configure(Argument::type(OptionsResolver::class))->shouldBeCalled();

        $result = $this->registry->getConfig('{"test": "service"}');
        $this->assertEquals(new Config('test', [
            'test' => 'service',
        ]), $result);
    }

    /**
     * If a invalid JSON encoded string is passed to getConfig, then it should throw an exception.
     *
     */
    public function testGetConfigJsonStringInvalid(): void
    {
        $this->expectException(ParsingException::class);
        $this->registry->setService('service', $this->service1->reveal());

        $result = $this->registry->getConfig('{tes  t: se  rvice');
        $this->assertEquals(new Config('test', [
            'test' => 'service',
        ]), $result);
    }

    public function testServiceDirectly(): void
    {
        $this->registry->setService('service', $this->service1->reveal());

        $result = $this->registry->getConfig('service');
        $this->assertEquals(new Config('test', [
            'test' => 'service',
        ]), $result);
    }
}
