<?php
/**
 * For the full copyright and license information, please view the
 * docs/licenses/LICENSE.txt file that was distributed with this source code.
 */

namespace Tests\Unit\PrestaShopBundle\Routing\Converter;

use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagManager;
use PrestaShopBundle\Routing\Converter\Exception\RouteNotFoundException;
use PrestaShopBundle\Routing\Converter\LegacyRoute;
use PrestaShopBundle\Routing\Converter\LegacyRouteFactory;
use PrestaShopBundle\Routing\Converter\RouterProvider;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;

class RouterProviderTest extends TestCase
{
    public function testBuildRoutes()
    {
        $router = $this->buildMultipleRouterMock([
            [
                'route_name' => 'admin_products',
                'path' => '/products',
                '_legacy_link' => 'AdminProducts',
            ],
            [
                'route_name' => 'admin_products_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminProducts:add',
                    'AdminProducts:create',
                ],
            ],
        ]);

        $routerProvider = new RouterProvider(
            $router,
            new LegacyRouteFactory($this->createMock(FeatureFlagManager::class))
        );

        $legacyRoutes = $routerProvider->getLegacyRoutes();
        $this->assertCount(2, $legacyRoutes);
        $this->assertNotEmpty($legacyRoutes['admin_products']);

        /** @var LegacyRoute $legacyRoute */
        $legacyRoute = $legacyRoutes['admin_products'];
        $this->assertEquals('admin_products', $legacyRoute->getRouteName());
        $this->assertSame(['AdminProducts' => [
            'index' => 'admin_products',
        ]], $legacyRoute->getControllersActions());

        $legacyRoute = $legacyRoutes['admin_products_create'];
        $this->assertEquals('admin_products_create', $legacyRoute->getRouteName());
        $this->assertSame(['AdminProducts' => [
            'add' => 'admin_products_create',
            'create' => 'admin_products_create',
        ]], $legacyRoute->getControllersActions());
    }

    public function testControllersActions()
    {
        $router = $this->buildMultipleRouterMock([
            [
                'route_name' => 'admin_products',
                'path' => '/products',
                '_legacy_link' => 'AdminProducts',
            ],
            [
                'route_name' => 'admin_products_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminProducts:add',
                    'AdminProducts:create',
                ],
            ],
            [
                'route_name' => 'admin_categories_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminCategories:add',
                    'AdminCategories:create',
                ],
            ],
        ]);
        $routerProvider = new RouterProvider(
            $router,
            new LegacyRouteFactory($this->createMock(FeatureFlagManager::class))
        );
        $controllersActions = $routerProvider->getControllersActions();
        $this->assertCount(2, $controllersActions);
        $this->assertSame([
            'AdminProducts' => [
                'index' => 'admin_products',
                'add' => 'admin_products_create',
                'create' => 'admin_products_create',
            ],
            'AdminCategories' => [
                'add' => 'admin_categories_create',
                'create' => 'admin_categories_create',
            ],
        ], $controllersActions);
    }

    public function testGetActionsByController()
    {
        $router = $this->buildMultipleRouterMock([
            [
                'route_name' => 'admin_products',
                'path' => '/products',
                '_legacy_link' => 'AdminProducts',
            ],
            [
                'route_name' => 'admin_products_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminProducts:add',
                    'AdminProducts:create',
                ],
            ],
            [
                'route_name' => 'admin_categories_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminCategories:add',
                    'AdminCategories:create',
                ],
            ],
        ]);

        $routerProvider = new RouterProvider(
            $router,
            new LegacyRouteFactory($this->createMock(FeatureFlagManager::class))
        );

        $controllerActions = $routerProvider->getActionsByController('AdminProducts');
        $this->assertCount(3, $controllerActions);
        $this->assertSame(['index', 'add', 'create'], $controllerActions);
    }

    public function testGetActionsByControllerInsensitive()
    {
        $router = $this->buildMultipleRouterMock([
            [
                'route_name' => 'admin_products',
                'path' => '/products',
                '_legacy_link' => 'AdminProducts',
            ],
            [
                'route_name' => 'admin_products_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminProducts:add',
                    'AdminProducts:create',
                ],
            ],
            [
                'route_name' => 'admin_categories_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminCategories:add',
                    'AdminCategories:create',
                ],
            ],
        ]);

        $routerProvider = new RouterProvider(
            $router,
            new LegacyRouteFactory($this->createMock(FeatureFlagManager::class))
        );

        $controllerActions = $routerProvider->getActionsByController('adminproducts');
        $this->assertCount(3, $controllerActions);
        $this->assertSame(['index', 'add', 'create'], $controllerActions);

        $controllerActions = $routerProvider->getActionsByController('adMinProDuctS');
        $this->assertCount(3, $controllerActions);
        $this->assertSame(['index', 'add', 'create'], $controllerActions);
    }

    public function testGetLegacyRouteByAction()
    {
        $router = $this->buildMultipleRouterMock([
            [
                'route_name' => 'admin_products',
                'path' => '/products',
                '_legacy_link' => 'AdminProducts',
            ],
            [
                'route_name' => 'admin_products_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminProducts:add',
                    'AdminProducts:create',
                ],
            ],
            [
                'route_name' => 'admin_categories_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminCategories:add',
                    'AdminCategories:create',
                ],
            ],
            [
                'route_name' => 'admin_modules_create',
                'path' => '/modules/create',
                '_legacy_link' => [
                    'AdminModules:add',
                    'AdminModules:create',
                ],
            ],
            [
                'route_name' => 'awesome_modules_create',
                'path' => '/module/awesome/modules/create',
                '_legacy_link' => [
                    'AdminModules:add',
                    'AdminModules:create',
                ],
            ],
        ]);

        $routerProvider = new RouterProvider(
            $router,
            new LegacyRouteFactory($this->createMock(FeatureFlagManager::class))
        );

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminProducts', 'index');
        $this->assertEquals('admin_products', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminProducts', 'list');
        $this->assertEquals('admin_products', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminProducts', '');
        $this->assertEquals('admin_products', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminProducts', null);
        $this->assertEquals('admin_products', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminProducts', 'add');
        $this->assertEquals('admin_products_create', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminProducts', 'create');
        $this->assertEquals('admin_products_create', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminCategories', 'create');
        $this->assertEquals('admin_categories_create', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminCategories', 'add');
        $this->assertEquals('admin_categories_create', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminModules', 'create');
        $this->assertEquals('admin_modules_create', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminModules', 'add');
        $this->assertEquals('admin_modules_create', $legacyRoute->getRouteName());
    }

    public function testGetLegacyRouteByActionInsensitive()
    {
        $router = $this->buildMultipleRouterMock([
            [
                'route_name' => 'admin_products',
                'path' => '/products',
                '_legacy_link' => 'AdminProducts',
            ],
            [
                'route_name' => 'admin_products_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminProducts:add',
                    'AdminProducts:create',
                ],
            ],
            [
                'route_name' => 'admin_categories_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminCategories:add',
                    'AdminCategories:create',
                ],
            ],
            [
                'route_name' => 'admin_modules_create',
                'path' => '/modules/create',
                '_legacy_link' => [
                    'AdminModules:add',
                    'AdminModules:create',
                ],
            ],
            [
                'route_name' => 'awesome_modules_create',
                'path' => '/module/awesome/modules/create',
                '_legacy_link' => [
                    'AdminModules:add',
                    'AdminModules:create',
                ],
            ],
        ]);

        $routerProvider = new RouterProvider(
            $router,
            new LegacyRouteFactory($this->createMock(FeatureFlagManager::class))
        );

        $legacyRoute = $routerProvider->getLegacyRouteByAction('adminproducts', 'index');
        $this->assertEquals('admin_products', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdMinProDucts', '');
        $this->assertEquals('admin_products', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('ADMINPRODUCTS', 'add');
        $this->assertEquals('admin_products_create', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('ADMINPRODUCTS', 'ADD');
        $this->assertEquals('admin_products_create', $legacyRoute->getRouteName());

        $legacyRoute = $routerProvider->getLegacyRouteByAction('AdminProducts', 'Create');
        $this->assertEquals('admin_products_create', $legacyRoute->getRouteName());
    }

    public function testControllerNotFound()
    {
        $router = $this->buildMultipleRouterMock([
            [
                'route_name' => 'admin_products',
                'path' => '/products',
                '_legacy_link' => 'AdminProducts',
            ],
            [
                'route_name' => 'admin_products_create',
                'path' => '/products/create',
                '_legacy_link' => [
                    'AdminProducts:add',
                    'AdminProducts:create',
                ],
            ],
        ]);

        $routerProvider = new RouterProvider(
            $router,
            new LegacyRouteFactory($this->createMock(FeatureFlagManager::class))
        );

        $caughtException = null;

        try {
            $routerProvider->getLegacyRouteByAction('AdminCategories', 'add');
        } catch (RouteNotFoundException $e) {
            $caughtException = $e;
        }
        $this->assertNotNull($caughtException);
        $this->assertEquals('Could not find a route matching for legacy controller: AdminCategories', $caughtException->getMessage());

        $caughtException = null;

        try {
            $routerProvider->getLegacyRouteByAction('AdminProducts', 'edit');
        } catch (RouteNotFoundException $e) {
            $caughtException = $e;
        }
        $this->assertNotNull($caughtException);
        $this->assertEquals('Could not find a route matching for legacy action: AdminProducts:edit', $caughtException->getMessage());
    }

    /**
     * @param array $routes
     *
     * @return MockObject|RouterInterface
     */
    private function buildMultipleRouterMock(array $routes)
    {
        $routeCollection = $this->buildRouteCollection($routes);

        $mockRouter = $this
            ->getMockBuilder(RouterInterface::class)
            ->disableOriginalConstructor()
            ->getMock();

        $mockRouter
            ->expects($this->once())
            ->method('getRouteCollection')
            ->willReturn($routeCollection);

        $mockRouter
            ->method('generate')
            ->will($this->returnCallback(
                function ($routeName) use ($routeCollection) {
                    $route = $routeCollection->get($routeName);

                    return null !== $route ? $route->getPath() : null;
                }
            ));

        return $mockRouter;
    }

    /**
     * @param array $routes
     *
     * @return RouteCollection
     */
    private function buildRouteCollection(array $routes)
    {
        $routeCollection = new RouteCollection();
        foreach ($routes as $route) {
            $routeDefaults = [
                '_legacy_link' => $route['_legacy_link'],
            ];
            if (!empty($route['_legacy_parameters'])) {
                $routeDefaults['_legacy_parameters'] = $route['_legacy_parameters'];
            }
            $routeCollection->add($route['route_name'], new Route(
                $route['path'],
                $routeDefaults
            ));
        }

        return $routeCollection;
    }
}
