<?php

namespace AutoAdmin\Bundle\Tests\Unit\Content\EventListener;

use AutoAdmin\Bundle\Content\Configuration\ConfigManager;
use AutoAdmin\Bundle\Content\Context\ContentContext;
use AutoAdmin\Bundle\Content\Entity\AbstractContent;
use AutoAdmin\Bundle\Content\Event\ContentMatchEvent;
use AutoAdmin\Bundle\Content\EventListener\ContentMatcherListener;
use AutoAdmin\Bundle\Content\Repository\DefaultTranslatableContentRepository;
use Doctrine\Persistence\ManagerRegistry;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use ReflectionClass;
use Symfony\Component\HttpFoundation\Request;

class ContentMatcherListenerTest extends TestCase
{
    /**
     * @dataProvider isSupportedRouteDataProvider
     */
    public function testIsSupportedRoute(string $route, bool $hasRoute, array $getRoute, bool $expected): void
    {
        $contentMatcherListener = $this->getMockBuilder(ContentMatcherListener::class)
            ->disableOriginalConstructor()
            ->getMock();

        $configManager = $this->getMockBuilder(ConfigManager::class)
            ->disableOriginalConstructor()
            ->getMock();

        $configManager->method('hasRoute')
            ->willReturn($hasRoute);

        $configManager->method('getRoute')
            ->willReturn($getRoute);

        $reflection = new ReflectionClass(ContentMatcherListener::class);
        $reflection->getProperty('config')->setValue($contentMatcherListener, $configManager);

        $method = $reflection->getMethod('isSupportedRoute');

        $this->assertEquals($expected, $method->invokeArgs($contentMatcherListener, [$route]));
    }

    /**
     * @dataProvider onContentMatchDataProvider
     */
    public function testOnContentMatch(
        bool $isSupportedRoute,
        ?string $route = null,
        ?string $contentClass = null,
        ?array $slugAndAction = null,
        ?string $warning = null,
        ?object $contentFindOneBy = null,
        ?string $error = null,
        ?array $getRoute = null,
        array $handleParameters = [],
        ?object $mainContent = null
    ): void
    {
        $contentMatcherListener = $this->getMockBuilder(ContentMatcherListener::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['isSupportedRoute', 'handleParameters'])
            ->getMock();

        $contentMatcherListener->method('isSupportedRoute')
            ->willReturn($isSupportedRoute);

        $contentMatcherListener->method('handleParameters')
            ->willReturn($handleParameters);

        $event = $this->getMockBuilder(ContentMatchEvent::class)
            ->disableOriginalConstructor()
            ->getMock();

        $contentContext = new ContentContext();
        $event->method('getContentContext')
            ->willReturn($contentContext);

        $request = $this->getMockBuilder(Request::class)
            ->disableOriginalConstructor()
            ->getMock();

        $request->method('get')
            ->willReturn($route);
        $request->method('getUri')
            ->willReturn('http://localhost');

        $event->method('getRequest')
            ->willReturn($request);

        $configManager = $this->getMockBuilder(ConfigManager::class)
            ->disableOriginalConstructor()
            ->getMock();

        $configManager->method('getContentClassForRoute')
            ->willReturn($contentClass);

        $configManager->method('getRouteSlugAndAction')
            ->willReturn($slugAndAction);

        $configManager->method('getRoute')
            ->willReturn($getRoute);

        $logger = $this->getMockBuilder(LoggerInterface::class)
            ->disableOriginalConstructor()
            ->getMock();

        if ($warning !== null) {
            $logger->expects($this->once())
                ->method('warning')
                ->with($warning);
        }

        if ($error !== null) {
            $logger->expects($this->once())
                ->method('error')
                ->with($error);
        }

        $doctrine = $this->getMockBuilder(ManagerRegistry::class)
            ->disableOriginalConstructor()
            ->getMock();

        $repository = $this->getMockBuilder(DefaultTranslatableContentRepository::class)
            ->disableOriginalConstructor()
            ->getMock();

        $repository->method('contentFindOneBy')
            ->willReturn($contentFindOneBy);

        $doctrine->method('getRepository')
            ->willReturn($repository);

        $reflection = new ReflectionClass(ContentMatcherListener::class);
        $reflection->getProperty('config')->setValue($contentMatcherListener, $configManager);
        $reflection->getProperty('logger')->setValue($contentMatcherListener, $logger);
        $reflection->getProperty('doctrine')->setValue($contentMatcherListener, $doctrine);

        $method = $reflection->getMethod('onContentMatch');

        $method->invokeArgs($contentMatcherListener, [$event]);

        $this->assertEquals($mainContent, $contentContext->getMainContent());
    }

    /**
     * @dataProvider handleParametersDataProvider
     */
    public function testHandleParameters(array $autofillParameters, array $get, array $expected): void
    {
        $contentMatcherListener = $this->getMockBuilder(ContentMatcherListener::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['isSupportedRoute', 'handleParameters'])
            ->getMock();

        $request = $this->getMockBuilder(Request::class)
            ->disableOriginalConstructor()
            ->getMock();

        $request->method('get')
            ->willReturnCallback(function (string $key) use ($get) {
                return $get[$key] ?? null;
            });

        $reflection = new ReflectionClass(ContentMatcherListener::class);

        $method = $reflection->getMethod('handleParameters');

        $result = $method->invokeArgs($contentMatcherListener, [$autofillParameters, $request]);

        $this->assertEquals($expected, $result);
    }

    private function isSupportedRouteDataProvider(): array
    {
        return [
            'page index' => [
                'page_index',
                false,
                [],
                true,
            ],
            'other route not exist' => [
                'other_route',
                false,
                [],
                false,
            ],
            'other route no config' => [
                'other_route',
                true,
                [],
                false,
            ],
            'other route bad config' => [
                'other_route',
                true,
                ['bad' => 'config'],
                false,
            ],
            'other route autofill_parameters empty' => [
                'other_route',
                true,
                ['autofill_parameters' => []],
                false,
            ],
            'other route' => [
                'other_route',
                true,
                ['autofill_parameters' => [
                    'param1' => 'value1',
                ]],
                true,
            ],
        ];
    }

    private function onContentMatchDataProvider(): array
    {
        $anonymousClass = new class extends AbstractContent {};
        $anonymousAbstractContentClass = new class extends AbstractContent {};

        return [
            'not supported route' => [
                'isSupportedRoute' => false,
            ],
            'supported route no content class' => [
                'isSupportedRoute' => true,
                'route' => 'page_index',
                'contentClass' => null,
                'slugAndAction' => null,
                'warning' => 'Could not match $mainContent on url "http://localhost" and route "page_index"',
            ],
            'supported route with content class no $slugAndAction' => [
                'isSupportedRoute' => true,
                'route' => 'page_index',
                'contentClass' => $anonymousClass::class,
            ],
            'supported route bad class' => [
                'isSupportedRoute' => true,
                'route' => 'page_index',
                'contentClass' => $anonymousClass::class,
                'slugAndAction' => ['slug' => 'slug', 'action' => 'action'],
            ],
            'page_index throw' => [
                'isSupportedRoute' => true,
                'route' => 'page_index',
                'contentClass' => $anonymousAbstractContentClass::class,
                'slugAndAction' => ['slug' => 'slug', 'action' => 'action'],
                'warning' => null,
                'contentFindOneBy' => null,
                'error' => 'You must define one page as "homepage".',
            ],
            'page_index success' => [
                'isSupportedRoute' => true,
                'route' => 'page_index',
                'contentClass' => $anonymousAbstractContentClass::class,
                'slugAndAction' => ['slug' => 'slug', 'action' => 'action'],
                'warning' => null,
                'contentFindOneBy' => $anonymousAbstractContentClass,
                'error' => null,
                'getRoute' => null,
                'handleParameters' => [],
                'mainContent' => $anonymousAbstractContentClass,
            ],
            'other_route no $routeConfig' => [
                'isSupportedRoute' => true,
                'route' => 'other_route',
                'contentClass' => $anonymousAbstractContentClass::class,
                'slugAndAction' => ['slug' => 'slug', 'action' => 'action'],
                'warning' => null,
                'contentFindOneBy' => null,
                'error' => null,
            ],
            'other_route no autofill_parameters' => [
                'isSupportedRoute' => true,
                'route' => 'other_route',
                'contentClass' => $anonymousAbstractContentClass::class,
                'slugAndAction' => ['slug' => 'slug', 'action' => 'action'],
                'warning' => null,
                'contentFindOneBy' => null,
                'error' => null,
                'getRoute' => ['autofill_parameters' => []],
            ],
            'other_route no handleParameters' => [
                'isSupportedRoute' => true,
                'route' => 'other_route',
                'contentClass' => $anonymousAbstractContentClass::class,
                'slugAndAction' => ['slug' => 'slug', 'action' => 'action'],
                'warning' => null,
                'contentFindOneBy' => null,
                'error' => null,
                'getRoute' => ['autofill_parameters' => [['param1' => 'value1']]],
            ],
            'other_route no contentFindOneBy' => [
                'isSupportedRoute' => true,
                'route' => 'other_route',
                'contentClass' => $anonymousAbstractContentClass::class,
                'slugAndAction' => ['slug' => 'slug', 'action' => 'action'],
                'warning' => null,
                'contentFindOneBy' => null,
                'error' => null,
                'getRoute' => ['autofill_parameters' => [['param1' => 'value1']]],
                'handleParameters' => ['param1' => 'value1'],
            ],
            'other_route success' => [
                'isSupportedRoute' => true,
                'route' => 'other_route',
                'contentClass' => $anonymousAbstractContentClass::class,
                'slugAndAction' => ['slug' => 'slug', 'action' => 'action'],
                'warning' => null,
                'contentFindOneBy' => $anonymousAbstractContentClass,
                'error' => null,
                'getRoute' => ['autofill_parameters' => [['param1' => 'value1']]],
                'handleParameters' => ['param1' => 'value1'],
                'mainContent' => $anonymousAbstractContentClass,
            ],
        ];
    }

    private function handleParametersDataProvider(): array
    {
        return [
            'empty autofillParameters' => [
                'autofillParameters' => [],
                'get' => [],
                'expected' => [],
            ],
            'currentTranslation' => [
                'autofillParameters' => [
                    'test' => 'currentTranslation.test',
                ],
                'get' => [
                    'test' => 'value',
                ],
                'expected' => [
                    '_tr.test' => 'value',
                ],
            ],
            'simple' => [
                'autofillParameters' => [
                    'test' => '_.test',
                ],
                'get' => [
                    'test' => 'value',
                ],
                'expected' => [
                    '_.test' => 'value',
                ],
            ],
            'no prefix' => [
                'autofillParameters' => [
                    'test' => 'test',
                ],
                'get' => [
                    'test' => 'value',
                ],
                'expected' => [
                    '_.test' => 'value',
                ],
            ],
            'too long' => [
                'autofillParameters' => [
                    'test' => '_.test._.test',
                ],
                'get' => [],
                'expected' => [],
            ],
            'multiple' => [
                'autofillParameters' => [
                    'test' => '_.test',
                    'test2' => '_.test2',
                ],
                'get' => [
                    'test' => 'value',
                    'test2' => 'value2',
                ],
                'expected' => [
                    '_.test' => 'value',
                    '_.test2' => 'value2',
                ],
            ],
        ];
    }
}
