<?php

/**
 * Questo file è parte di Pongho.
 *
 * @author    Daniele Termini
 * @copyright Web Agency Meta Line S.r.l.
 * @package   Application\Admin
 */

namespace Application\Admin\Tests\AdminSidebar;

use Application\Admin\AdminSidebar\Sidebar;
use Application\Admin\Utilities\PonghoUrl;
use Application\Core\Model\Site;
use PHPUnit\Framework\MockObject\MockObject;
use Pongho\Http\Exception\HttpNotFoundException;
use Pongho\Http\Request;
use Symfony\Component\DomCrawler\Crawler;

class SidebarTest extends \PHPUnit\Framework\TestCase
{
    public function testAddPanel(): void
    {
        $sidebar = $this->getSidebar('/');
        $sidebar->addPanel('Panel', 'panelname', 'css-class', 0);

        $this->assertInstanceOf(\Application\Admin\AdminSidebar\Panel::class, $sidebar->getPanel('panelname'));
    }

    public function testAddItemNoPanel(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $sidebar = $this->getSidebar('/');
        $sidebar->addItem('Item', 'path', 'panelname', 0, 'SomeApplication\\SomeController');
    }

    public function testAddItem(): void
    {
        $sidebar = $this->getSidebar('/');
        $sidebar->addPanel('Panel', 'panelname', 'css-class', 0);
        $sidebar->addItem('Item', '/path', 'panelname', 0, 'SomeApplication\\SomeController');

        $this->assertInstanceOf(\Application\Admin\AdminSidebar\Item::class, $sidebar->getItem('/path'));
        $this->assertEquals('SomeApplication\\SomeController', $sidebar->getController('/path'));
    }

    public function testPanelUniqueness(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $sidebar = $this->getSidebar('/');
        $sidebar->addPanel('Panel', 'panelname', 'css-class', 0);
        $sidebar->addPanel('Another Panel', 'panelname', 'some-icon', 10);
    }

    /**
     * @return array
     */
    public function duplicateItemProvider()
    {
        return [
            [
                [
                    'panelname' => [
                        'label'    => 'Panel',
                        'class'    => 'css-class',
                        'position' => 10,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Item',
                                'url'        => 'path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                            [
                                'label'      => 'Duplicate Item',
                                'url'        => 'path',
                                'position'   => 20,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                ],
            ],
            [
                [
                    'panelname' => [
                        'label'    => 'Panel',
                        'class'    => 'css-class',
                        'position' => 10,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Item',
                                'url'        => 'path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                            [
                                'label'      => 'Duplicate Item',
                                'url'        => '/path/',
                                'position'   => 20,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                ],
            ],
            [
                [
                    'panelname' => [
                        'label'    => 'Panel',
                        'class'    => 'css-class',
                        'position' => 10,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Item',
                                'url'        => 'path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                            [
                                'label'      => 'Duplicate Item',
                                'url'        => 'path/',
                                'position'   => 20,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                ],
            ],
            [
                [
                    'panelname' => [
                        'label'    => 'Panel',
                        'class'    => 'css-class',
                        'position' => 10,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Item',
                                'url'        => 'path/a',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                            [
                                'label'      => 'Duplicate Item',
                                'url'        => 'path/a/',
                                'position'   => 20,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                ],
            ],
            [
                [
                    'panelname' => [
                        'label'    => 'Panel',
                        'class'    => 'css-class',
                        'position' => 10,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Item',
                                'url'        => 'path/a',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                            [
                                'label'      => 'Duplicate Item',
                                'url'        => '/path/a/',
                                'position'   => 20,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                ],
            ],
            [
                [
                    'panelname'    => [
                        'label'    => 'Panel',
                        'class'    => 'css-class',
                        'position' => 10,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Item',
                                'url'        => 'path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                    'anotherpanel' => [
                        'label'    => 'Another Panel',
                        'class'    => 'css-class',
                        'position' => 20,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Duplicate Item',
                                'url'        => 'path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                ],
            ],
        ];
    }

    /**
     * @dataProvider duplicateItemProvider
     */
    public function testItemUniqueness(array $panel_config): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->buildConfig($panel_config);
    }

    /**
     * @param $panel_config
     * @return \Application\Admin\AdminSidebar\Sidebar
     */
    protected function buildConfig(array $panel_config)
    {
        $sidebar = $this->getSidebar('/');

        foreach ($panel_config as $panel => $config) {
            $sidebar->addPanel($config['label'], $panel, $config['class'], $config['position'], $config['module']);

            foreach ($config['items'] as $item) {
                $sidebar->addItem($item['label'], $item['url'], $panel, $item['position'], $item['controller']);
            }
        }

        return $sidebar;
    }

    /**
     * @return array
     */
    public function removePanelProvider()
    {
        return [
            [
                [
                    'panelname' => [
                        'label'    => 'Panel',
                        'class'    => 'css-class',
                        'position' => 10,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Item',
                                'url'        => 'path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                            [
                                'label'      => 'Another Item',
                                'url'        => 'another-path',
                                'position'   => 20,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                ],
                'panelname', // removed panel
                0, // panel count
            ],
            [
                [
                    'panelname'    => [
                        'label'    => 'Panel',
                        'class'    => 'css-class',
                        'position' => 10,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Item',
                                'url'        => 'path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                    'anotherpanel' => [
                        'label'    => 'Another Panel',
                        'class'    => 'css-class',
                        'position' => 20,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Another Item',
                                'url'        => 'another-path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                ],
                'panelname', // removed panel
                1, // panel count
            ],
        ];
    }

    /**
     * @dataProvider removePanelProvider
     */
    public function testRemovePanel(array $panel_config, $remove_panel, $panel_count): void
    {
        $sidebar = $this->buildConfig($panel_config);

        $sidebar->removePanel($remove_panel);

        $panels = $sidebar->getPanels();

        $this->assertFalse(array_key_exists($remove_panel, $panels));
        $this->assertEquals($panel_count, count($panels));
    }

    /**
     * @return array
     */
    public function removeItemProvider()
    {
        return [
            [
                [
                    'panelname' => [
                        'label'    => 'Panel',
                        'class'    => 'css-class',
                        'position' => 10,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Item',
                                'url'        => 'path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                            [
                                'label'      => 'Another Item',
                                'url'        => 'another-path',
                                'position'   => 20,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                ],
                '/path/', // removed item
            ],
            [
                [
                    'panelname'    => [
                        'label'    => 'Panel',
                        'class'    => 'css-class',
                        'position' => 10,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Item',
                                'url'        => 'path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                    'anotherpanel' => [
                        'label'    => 'Another Panel',
                        'class'    => 'css-class',
                        'position' => 20,
                        'module'   => null,
                        'items'    => [
                            [
                                'label'      => 'Another Item',
                                'url'        => 'another-path',
                                'position'   => 10,
                                'controller' => 'SomeApplication\\SomeController',
                            ],
                        ],
                    ],
                ],
                '/path/', // removed item
            ],
        ];
    }

    /**
     * @dataProvider removeItemProvider
     */
    public function testRemoveItem(array $panel_config, $remove_item): void
    {
        $sidebar = $this->buildConfig($panel_config);

        $sidebar->removeItem($remove_item);

        $panels = $sidebar->getPanels();

        $this->assertFalse(array_key_exists($remove_item, $panels));
    }

    public function testPanelsOrder(): void
    {
        $sidebar = $this->getSidebar('/');

        $sidebar->addPanel('panel A', 'a', '', 10);
        $this->assertEquals(['a'], array_keys($sidebar->getPanels()));

        $sidebar->addPanel('panel B', 'b', '', 30);
        $this->assertEquals(['a', 'b'], array_keys($sidebar->getPanels()));

        $sidebar->addPanel('panel C', 'c', '', 20);
        $this->assertEquals(['a', 'c', 'b'], array_keys($sidebar->getPanels()));

        $sidebar->removePanel('b');
        $this->assertEquals(['a', 'c'], array_keys($sidebar->getPanels()));

        $sidebar->addPanel('panel B', 'b', '', 15);
        $this->assertEquals(['a', 'b', 'c'], array_keys($sidebar->getPanels()));
    }

    public function testItemsOrder(): void
    {
        $sidebar = $this->getSidebar('/');
        $sidebar->addPanel('Panel', 'panelname', '', 0);

        $sidebar->addItem('item A', 'path/a/', 'panelname', 20, 'Controller');
        $this->assertEquals(['/path/a/'], array_keys($sidebar->getItems('panelname')));

        $sidebar->addItem('item B', 'path/b/', 'panelname', 9999, 'Controller');
        $this->assertEquals(['/path/a/', '/path/b/'], array_keys($sidebar->getItems('panelname')));

        $sidebar->addItem('item C', 'path/c/', 'panelname', 10, 'Controller');
        $this->assertEquals(['/path/c/', '/path/a/', '/path/b/'], array_keys($sidebar->getItems('panelname')));

        $sidebar->removeItem('/path/b/');
        $this->assertEquals(['/path/c/', '/path/a/'], array_keys($sidebar->getItems('panelname')));

        $sidebar->addItem('item B', 'path/b/', 'panelname', 15, 'Controller');
        $this->assertEquals(['/path/c/', '/path/b/', '/path/a/'], array_keys($sidebar->getItems('panelname')));
    }

    protected function prepareSidebar()
    {
        $sidebar = $this->getSidebar('/path/a/', 'domain.com');

        $sidebar->addPanel('Panel A', 'panela', '', 0);
        $sidebar->addItem('item 0', '/path/', 'panela', 10, 'Controller');
        $sidebar->addItem('item 1', '/path/a/', 'panela', 20, 'Controller');
        $sidebar->addItem('item 2', '/path/b/', 'panela', 30, 'Controller');
        $sidebar->addItem('item 3', '/path-other/', 'panela', 40, 'Controller');

        $sidebar->addPanel('Panel B', 'panelb', '', 10);
        $sidebar->addItem('item 4', '/foo/', 'panelb', 10, 'Controller');

        return $sidebar;
    }

    public function testActiveItem(): void
    {
        $sidebar = $this->prepareSidebar();

        try {
            $item = $sidebar->getActiveItem();
            $this->assertTrue($item->isActive());
            $this->assertEquals('/path/a/', $item->getPath());
        } catch (HttpNotFoundException) {
            $this->fail('The test is misconfigured, check the configuration');
        }
    }

    public function testRender(): void
    {
        $sidebar = $this->prepareSidebar();

        $render = '<ul id="sidebar">' . $sidebar->render() . '</ul>';

        $crawler = new Crawler($render);

        // Debug
        // echo $render;
        // $this->assertEquals(1, $crawler->filter('ul#sidebar')->count());

        // Controllo l'esistenza di 2 pannelli <li> nella sidebar
        $this->assertEquals(2, $crawler->filter('ul#sidebar > li')->count());

        // Controllo che un solo <li> abbia la classe "active"
        $active_panel = $crawler->filter('ul#sidebar > li.active');
        $this->assertEquals(1, $active_panel->count());

        // Controllo che il pannello attivo abbia la classe "dropdown"
        $classes = [];
        if (($attr = $active_panel->attr('class')) !== null) {
            $classes = explode(' ', $attr);
        }

        $this->assertTrue(in_array('dropdown', $classes));

        // Controllo che il pannello abbia uno <span> e un <ul>
        $this->assertEquals(1, $active_panel->filter('li > span')->count());
        $this->assertEquals(1, $active_panel->filter('li > ul')->count());

        // Controllo che ci siano 4 <li> nel pannello attivo
        $this->assertEquals(4, $active_panel->filter('ul > li')->count());

        //... e che uno di essi, e solo uno abbia a sua volta la classe "active"
        $active_item = $active_panel->filter('ul > li.active');
        $this->assertEquals(1, $active_item->count());

        // Controllo che l'item che ha la classe "active" sia quello che effettivamente ho scelto di attivare
        $this->assertEquals('item 1', $active_item->filter('a')->attr('title'));

        // Controllo che ci sia un altro pannello con un solo item, che non sia attivo e che non abbia la classe "dropdown"
        $second_panel = $crawler->filter('ul#sidebar > li')->eq(1);
        $classes = [];
        if (($attr = $second_panel->attr('class')) !== null) {
            $classes = explode(' ', $attr);
        }

        $this->assertFalse(in_array('active', $classes));
        $this->assertFalse(in_array('dropdown', $classes));

        // Controllo che il contenuto sia un link
        $this->assertEquals(0, $second_panel->filter('li > ul')->count());
        $this->assertEquals(0, $second_panel->filter('li > span')->count());
        $this->assertEquals(1, $second_panel->filter('li > a')->count());
    }

    public function testPanelSingleItem(): void
    {
        $sidebar = $this->getSidebar('/', 'domain.com');

        $sidebar->addPanel('Default', 'panel', '', 0);
        $sidebar->addItem('item', '/', 'panel', 10, 'Controller');

        $panel = $sidebar->addPanel('Panel A', 'panela', '', 10);
        $panel->forceDropdown(false);

        $sidebar->addItem('item a', '/path/a/', 'panela', 10, 'Controller');

        $panel = $sidebar->addPanel('Panel B', 'panelb', '', 20);
        $panel->forceDropdown();

        $sidebar->addItem('item b', '/path/b/', 'panelb', 10, 'Controller');

        $panel = $sidebar->addPanel('Panel C', 'panelc', '', 30);
        $panel->forceDropdown();

        $render = '<ul id="sidebar">' . $sidebar->render() . '</ul>';
        $crawler = new Crawler($render);

        $this->assertEquals(1, $crawler->filter('#sidebar > li.active > a')->count());
        $this->assertEquals(1, $crawler->filter('#sidebar > li:nth-child(2) > a')->count());
        $this->assertEquals(1, $crawler->filter('#sidebar > li:nth-child(3) > span')->count());
    }

    /**
     * @param $domain
     * @param $path
     * @return Sidebar
     */
    protected function prepareSidebarRoutes($path, $domain = '')
    {
        $sidebar = new Sidebar($this->getSiteMock($domain), $this->getRequestMock($path), $this->getPonghoUrlMock($domain));
        $sidebar->addRoute('/path/', 'StuffController');
        $sidebar->addPanel('Dashboard', 'dashboard', '', 0);
        $sidebar->addItem('Dashboard', '/', 'dashboard', 0, 'DashboardController');

        return $sidebar;
    }

    public function testSidebarRoutes(): void
    {
        $sidebar = $this->prepareSidebarRoutes('/path/', 'domain.com');

        $this->assertEquals('StuffController', $sidebar->getActiveItem()->getController());
    }

    public function testSidebarRemoveRoute(): void
    {
        $sidebar = $this->prepareSidebarRoutes('/path/', 'domain.com');

        $sidebar->removeItem('/path/');

        $this->assertNull($sidebar->getActiveItem());
    }

    public function pathProvider()
    {
        return [
            ['/', 1],
            ['/path/', 0],
        ];
    }

    /**
     * @param $path
     * @param $count
     * @dataProvider pathProvider
     */
    public function testSidebarRenderRoutes($path, $count): void
    {
        $sidebar = $this->prepareSidebarRoutes($path, 'domain.com');

        $render = '<ul id="sidebar">' . $sidebar->render() . '</ul>';
        $crawler = new Crawler($render);

        // La rotta non deve essere presente nel menu, mentre l'item sì
        $this->assertEquals(1, $crawler->filter('#sidebar > li')->count());
        $this->assertEquals($count, $crawler->filter('.active')->count());
    }

    /**
     * Verifica che una rotta linkata ad un item attivi l'item in fase di render
     */
    public function testSidebarLinkedRoutes(): void
    {
        $sidebar = new Sidebar($this->getSiteMock('domain.com'), $this->getRequestMock('path/b/'), $this->getPonghoUrlMock('domain.com'));
        $sidebar->addPanel('Panel', 'panel', '', 0);

        $item = $sidebar->addItem('item a', '/path/a/', 'panel', 10, 'Controller');
        $sidebar->addRoute('/path/b/', 'StuffController', null, $item);

        $render = '<ul id="sidebar">' . $sidebar->render() . '</ul>';
        $crawler = new Crawler($render);

        $this->assertEquals(1, $crawler->filter('.active')->count());
    }

    /**
     * @param      $uri
     * @return Sidebar
     */
    protected function getSidebar($uri, $domain = 'https://example.com')
    {
        return new Sidebar(
            $this->getSiteMock($domain),
            $this->getRequestMock($uri),
            $this->getPonghoUrlMock($domain)
        );
    }

    protected function getSiteMock($domain = 'https://example.com'): Site|MockObject
    {
        $site = $this->createMock(\Application\Core\Model\Site::class);

        $site->expects($this->any())
            ->method('getDomain')
            ->will($this->returnValue($domain));

        return $site;
    }

    /**
     * @param $uri
     * @return Request
     */
    protected function getRequestMock($uri)
    {
        return Request::create($uri);
    }

    /**
     * @param $domain
     */
    protected function getPonghoUrlMock($domain): PonghoUrl|MockObject
    {
        $pongho_url = $this->createMock(\Application\Admin\Utilities\PonghoUrl::class);

        $pongho_url
            ->expects($this->any())
            ->method('url')
            ->will($this->returnCallback(function ($path) use ($domain) {
                return $domain . $path . '?foo=bar';
            }));

        return $pongho_url;
    }
}
