<?php

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

namespace Application\Admin\Tests\Panels;

use Application\Admin\Panels\PanelInterface;
use Application\Admin\Panels\PanelsManager;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\DomCrawler\Crawler;

class PanelsManagerTest extends \PHPUnit\Framework\TestCase
{
    public function testCollection(): void
    {
        $manager = new PanelsManager();
        $this->assertFalse($manager->has('foo'));
        $this->assertEquals([], $manager->keys());

        $panel = $this->getPanelMock('foo');

        $manager->add($panel);
        $this->assertTrue($manager->has('foo'));
        $this->assertSame($panel, $manager->get('foo'));
        $this->assertEquals(['foo'], $manager->keys());

        $manager->del('foo');
        $this->assertFalse($manager->has('foo'));
        $this->assertEquals([], $manager->keys());
    }

    public function testAddConstrainsStringKey(): void
    {
        $this->expectException(\LogicException::class);
        $manager = new PanelsManager();
        $manager->add($this->getPanelMock(null));
    }

    public function testAddFirst(): void
    {
        $manager = new PanelsManager();

        $manager->add($this->getPanelMock('foo'));
        $manager->add($this->getPanelMock('bar'));
        $manager->addFirst($this->getPanelMock('baz'));

        $this->assertEquals(['baz', 'foo', 'bar'], $manager->keys());
    }

    public function testAddFirstConstrainsStringKey(): void
    {
        $this->expectException(\LogicException::class);
        $manager = new PanelsManager();
        $manager->addFirst($this->getPanelMock(null));
    }

    public function testNotFoundException(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $manager = new PanelsManager();
        $manager->get('foo');
    }

    public function testTraversable(): void
    {
        $manager = new PanelsManager();
        $this->assertInstanceOf('\Traversable', $manager);

        $manager->add($this->getPanelMock('foo'));
        $manager->add($this->getPanelMock('bar'));
        $this->assertEquals(['foo', 'bar'], $manager->keys());

        $counter = 0;
        foreach ($manager as $panel) {
            $this->assertInstanceOf(PanelInterface::class, $panel);
            $counter++;
        }

        $this->assertEquals(2, $counter);
    }

    public function testRender(): void
    {
        $manager = new PanelsManager();

        $manager->add($this->getPanelForRenderMock('foo', 'Foo Title', 'Foo Content'));
        $manager->add($this->getPanelForRenderMock('bar', 'Bar Title', 'Bar Content'));
        $manager->add($this->getPanelForRenderMock('baz', 'Baz Title', 'Baz Content'));

        $crawler = new Crawler($manager->render());

        $this->assertEquals(3, $crawler->filter('ul.tabs li a')->count());
        $this->assertEquals(1, $crawler->filter('ul.tabs li a[href="#panel-foo"]')->count());
        $this->assertEquals(1, $crawler->filter('ul.tabs li a[href="#panel-bar"]')->count());
        $this->assertEquals(1, $crawler->filter('ul.tabs li a[href="#panel-baz"]')->count());

        $this->assertEquals(1, $crawler->filter('div.panels')->count());
        $this->assertEquals(3, $crawler->filter('div.panel')->count());
        $this->assertEquals(1, $crawler->filter('div.panels #panel-foo')->count());
        $this->assertEquals(1, $crawler->filter('div.panels #panel-bar')->count());
        $this->assertEquals(1, $crawler->filter('div.panels #panel-baz')->count());
    }

    /**
     * @dataProvider externalLinkProvider
     * @param string $url
     * @param array  $paths
     */
    public function testRenderWithExternalLink($url, $paths): void
    {
        $pongho_url = $this->createMock(\Application\Admin\Utilities\PonghoUrl::class);

        $pongho_url
            ->expects($this->any())
            ->method('url')
            ->willReturnCallback(
                function ($url) {
                    return $url;
                }
            );

        /**
         * @var \Application\Admin\Utilities\PonghoUrl $pongho_url
         */

        $manager = new PanelsManager(true, $url, $pongho_url);

        $manager->add($this->getPanelForRenderWithExternalLinkMock('foo', 'Foo Title', 'Foo Content', true));
        $manager->add($this->getPanelForRenderWithExternalLinkMock('bar', 'Bar Title', 'Bar Content', false));
        $manager->add($this->getPanelForRenderWithExternalLinkMock('baz', 'Baz Title', 'Baz Content', false));

        $crawler = new Crawler($manager->render());

        $tabs = $crawler->filter('ul.tabs.nojs');
        $this->assertEquals(1, $tabs->count());

        $tabs_links = $tabs->filter('li a');
        $this->assertEquals(3, $tabs_links->count());

        /** @var \DOMElement $link */
        $links = [];
        foreach ($tabs_links as $link) {
            $links[] = $link->getAttribute('href');
        }

        $this->assertEquals($paths, $links);

        $this->assertEquals(1, $crawler->filter('div.panels')->count());
        $this->assertEquals(1, $crawler->filter('div.panel')->count());
        $this->assertEquals(1, $crawler->filter('div.panels #panel-foo')->count());
        $this->assertEquals(0, $crawler->filter('div.panels #panel-bar')->count());
        $this->assertEquals(0, $crawler->filter('div.panels #panel-baz')->count());
    }

    public function externalLinkProvider()
    {
        return [
            ['/foo', ['/foo/', '/foo/bar/', '/foo/baz/']],
            ['/foo/', ['/foo/', '/foo/bar/', '/foo/baz/']],
        ];
    }

    public function testExternalLinkWithoutRequestThrowException(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        new PanelsManager(true);
    }

    public function testFirstPanelIsActive(): void
    {
        $manager = new PanelsManager();

        $manager->add($this->getPanelForRenderMock('foo', 'Foo Title', 'Foo Content'));
        $manager->add($this->getPanelForRenderMock('bar', 'Bar Title', 'Bar Content'));
        $manager->add($this->getPanelForRenderMock('baz', 'Baz Title', 'Baz Content'));

        $crawler = new Crawler($manager->render());

        $this->assertSame(
            $crawler->filter('ul.tabs li.active a')->text(),
            $crawler->filter('ul.tabs li')->first()->filter('a')->text()
        );
    }

    public function testSetActivePanel(): void
    {
        $manager = new PanelsManager();

        $manager->add($this->getPanelForRenderMock('foo', 'Foo Title', 'Foo Content'));
        $manager->add($this->getPanelForRenderMock('bar', 'Bar Title', 'Bar Content'));
        $manager->add($this->getPanelForRenderMock('baz', 'Baz Title', 'Baz Content'));

        $manager->setActive('bar');

        $crawler = new Crawler($manager->render());

        $this->assertSame(
            $crawler->filter('ul.tabs li.active a')->text(),
            $crawler->filter('ul.tabs li')->eq(1)->filter('a')->text()
        );
    }

    public function testExceptionWhenSetActiveAPanelThatNotExists(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $manager = new PanelsManager();
        $manager->setActive('foo');
    }

    /**
     * @param string $key
     */
    private function getPanelMock($key): PanelInterface|MockObject
    {
        $panel = $this->createMock(PanelInterface::class);

        $panel->expects($this->once())->method('getKey')->willReturn($key);

        return $panel;
    }

    /**
     * @param string $key
     * @param string $title
     * @param string $content
     */
    private function getPanelForRenderMock($key, $title, $content): PanelInterface|MockObject
    {
        $panel = $this->createMock(PanelInterface::class);

        $panel->expects($this->any())->method('getKey')->willReturn($key);
        $panel->expects($this->once())->method('getTitle')->willReturn($title);
        $panel->expects($this->once())->method('getContent')->willReturn($content);

        return $panel;
    }

    /**
     * @param string $key
     * @param string $title
     * @param string $content
     * @param bool   $is_active
     */
    private function getPanelForRenderWithExternalLinkMock($key, $title, $content, $is_active): PanelInterface|MockObject
    {
        $panel = $this->createMock(PanelInterface::class);

        $panel->expects($this->any())->method('getKey')->willReturn($key);
        $panel->expects($this->once())->method('getTitle')->willReturn($title);

        if ($is_active) {
            $panel->expects($this->once())->method('getContent')->willReturn($content);
        } else {
            $panel->expects($this->never())->method('getContent')->willReturn($content);
        }

        return $panel;
    }
}
