<?php declare(strict_types=1);

namespace Newland\Toubiz\Poi\Neos\Tests\Unit\LinkHandler\Filter\Items;

use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\Service\NodeTypeManager;
use Neos\Flow\Tests\FunctionalTestCase;
use Neos\Neos\Domain\Service\ContentContextFactory;
use Newland\NeosCommon\Service\ControllerContextFactory;
use Newland\NeosTestingHelpers\InteractsWithNodes;
use Newland\Toubiz\Api\ObjectAdapter\Concern\ArticleConstants;
use Newland\Toubiz\Poi\Neos\Filter\ArticleClientFilterService;
use Newland\Toubiz\Poi\Neos\Filter\ArticleFilterFactory;
use Newland\Toubiz\Poi\Neos\Filter\Items\ArticleRoot;
use Newland\Toubiz\Poi\Neos\Service\LinkListService;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Enum\ArticleType;
use Newland\Toubiz\Sync\Neos\Tests\Factory\ArticleFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\AttributeFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\CategoryFactory;

class ArticleRootTest extends FunctionalTestCase
{
    use InteractsWithNodes;
    protected static $testablePersistenceEnabled = true;

    public function testAppliesMainTypeFilterAutomatically(): void
    {
        [ $tour1, $tour2 ] = (new ArticleFactory($this->objectManager))->createMultiple(
            2,
            [ 'mainType' => ArticleConstants::TYPE_TOUR ]
        );
        [ $attr1, $attr2 ] = (new ArticleFactory($this->objectManager))->createMultiple(
            2,
            [ 'mainType' => ArticleConstants::TYPE_ATTRACTION ]
        );

        $filter = new ArticleRoot();
        $filter->setArticleType(ArticleConstants::TYPE_TOUR);

        $ids = array_map(
            function (Article $article) {
                return $article->getPersistenceObjectIdentifier();
            },
            $filter->getArticleQuery()->getQuery()->execute()
        );

        $this->assertContains($tour1->getPersistenceObjectIdentifier(), $ids);
        $this->assertContains($tour2->getPersistenceObjectIdentifier(), $ids);
        $this->assertNotContains($attr1->getPersistenceObjectIdentifier(), $ids);
        $this->assertNotContains($attr2->getPersistenceObjectIdentifier(), $ids);
    }

    public function testCountsArticles(): void
    {
        (new ArticleFactory($this->objectManager))->createMultiple(2, [ 'mainType' => ArticleConstants::TYPE_TOUR ]);
        (new ArticleFactory($this->objectManager))->createMultiple(
            2,
            [ 'mainType' => ArticleConstants::TYPE_ATTRACTION ]
        );
        $this->assertEquals(4, $this->getRoot()->countArticles());
    }

    public function testCountsArticlesWithMainTypeSet(): void
    {
        (new ArticleFactory($this->objectManager))->createMultiple(2, [ 'mainType' => ArticleConstants::TYPE_TOUR ]);
        (new ArticleFactory($this->objectManager))->createMultiple(
            3,
            [ 'mainType' => ArticleConstants::TYPE_ATTRACTION ]
        );

        $this->assertEquals(3, $this->getRoot(ArticleConstants::TYPE_ATTRACTION)->countArticles());
    }

    public function testAddsClientFilterBasedOnSiteNode(): void
    {
        $site = $this->initializeSite('foo-bar');
        $node = $this->initializeNode('/sites/foo-bar', null, $site);

        $filter = $this->getRoot(
            ArticleConstants::TYPE_TOUR,
            [
                'foo-bar' => [
                    'mainType' => [
                        'values' => [
                            ArticleConstants::TYPE_TOUR => 'foo-bar-client',
                        ],
                    ],
                ],
            ],
            $node
        );


        [ $currentClientTour1, $currentClientTour2 ] = (new ArticleFactory($this->objectManager))->createMultiple(
            2,
            [ 'client' => 'foo-bar-client', 'mainType' => ArticleConstants::TYPE_TOUR ]
        );
        [ $currentClientAttr1, $currentClientAttr2 ] = (new ArticleFactory($this->objectManager))->createMultiple(
            2,
            [ 'client' => 'foo-bar-client', 'mainType' => ArticleConstants::TYPE_ATTRACTION ]
        );
        [ $foreignClientTour1, $foreignClientTour2 ] = (new ArticleFactory($this->objectManager))->createMultiple(
            2,
            [ 'client' => 'another-client', 'mainType' => ArticleConstants::TYPE_TOUR ]
        );
        [ $foreignClientAttr1, $foreignClientAttr2 ] = (new ArticleFactory($this->objectManager))->createMultiple(
            2,
            [ 'client' => 'another-client', 'mainType' => ArticleConstants::TYPE_ATTRACTION ]
        );

        $ids = array_map(
            function (Article $article) {
                return $article->getPersistenceObjectIdentifier();
            },
            $filter->getArticleQuery()->getQuery()->execute()
        );

        $this->assertContains($currentClientTour1->getPersistenceObjectIdentifier(), $ids);
        $this->assertContains($currentClientTour2->getPersistenceObjectIdentifier(), $ids);
        $this->assertNotContains($currentClientAttr1->getPersistenceObjectIdentifier(), $ids);
        $this->assertNotContains($currentClientAttr2->getPersistenceObjectIdentifier(), $ids);
        $this->assertNotContains($foreignClientTour1->getPersistenceObjectIdentifier(), $ids);
        $this->assertNotContains($foreignClientTour2->getPersistenceObjectIdentifier(), $ids);
        $this->assertNotContains($foreignClientAttr1->getPersistenceObjectIdentifier(), $ids);
        $this->assertNotContains($foreignClientAttr2->getPersistenceObjectIdentifier(), $ids);
    }

    public function testGeneratesLinkToListBasedOnConfiguration(): void
    {
        $site = $this->initializeSite('foo-bar');
        $node = $this->initializeNode('/sites/foo-bar', null, $site);

        $filter = $this->getRoot(
            ArticleConstants::TYPE_TOUR,
            [],
            $node,
            [
                'default' => [
                    'lists' => [
                        ArticleConstants::TYPE_TOUR => 'https://foobar.com/tours',
                    ],

                ],
            ]
        );

        $context = $this->objectManager
            ->get(ControllerContextFactory::class)
            ->initializeFakeControllerContext($node);
        $uri = $filter->getListUri($context, [], null);
        $this->assertEquals('https://foobar.com/tours', $uri);
    }

    public function testUsesGivenRootInsteadOfConfigurationIfNotNull(): void
    {
        $site = $this->initializeSite('foo-bar');
        $node = $this->initializeNode('/sites/foo-bar', null, $site);

        $filter = $this->getRoot(
            ArticleConstants::TYPE_TOUR,
            [],
            $node,
            [
                'default' => [
                    'lists' => [
                        ArticleConstants::TYPE_TOUR => 'https://foobar.com/tours',
                    ],
                ],
            ]
        );

        $context = $this->objectManager
            ->get(ControllerContextFactory::class)
            ->initializeFakeControllerContext($node);
        $uri = $filter->getListUri($context, [], 'https://barfoo.com');
        $this->assertEquals('https://barfoo.com', $uri);
    }

    public function testDoesNotFindHiddenArticlesByDefault(): void
    {
        $site = $this->initializeSite('foo-bar');
        $node = $this->initializeNode('/sites/foo-bar', null, $site);

        $filter = $this->getRoot(
            ArticleConstants::TYPE_TOUR,
            [],
            $node,
            [
                'default' => [
                    'lists' => [
                        ArticleConstants::TYPE_TOUR => 'https://foobar.com/tours',
                    ],

                ],
            ]
        );

        $visible = (new ArticleFactory($this->objectManager))->create([ 'mainType' => ArticleConstants::TYPE_TOUR, 'hidden' => false ]);
        $hidden = (new ArticleFactory($this->objectManager))->create([ 'mainType' => ArticleConstants::TYPE_TOUR, 'hidden' => true ]);
        $results = $filter->getArticleQuery()->getQuery()->execute();

        $ids = [];
        foreach ($results as $result) {
            $ids[] = $result->getPersistenceObjectIdentifier();
        }

        $this->assertContains($visible->getPersistenceObjectIdentifier(), $ids);
        $this->assertNotContains($hidden->getPersistenceObjectIdentifier(), $ids);
    }

    public function testAllowsFindingHiddenArticlesWhenExplicitlySpecified(): void
    {
        $site = $this->initializeSite('foo-bar');
        $node = $this->initializeNode('/sites/foo-bar', null, $site);

        $filter = $this->getRoot(
            ArticleConstants::TYPE_TOUR,
            [],
            $node,
            [
                'default' => [
                    'lists' => [
                        ArticleConstants::TYPE_TOUR => 'https://foobar.com/tours',
                    ],

                ],
            ]
        );

        $visible = (new ArticleFactory($this->objectManager))->create([ 'mainType' => ArticleConstants::TYPE_TOUR, 'hidden' => false ]);
        $hidden = (new ArticleFactory($this->objectManager))->create([ 'mainType' => ArticleConstants::TYPE_TOUR, 'hidden' => true ]);
        $results = $filter->getArticleQuery([], true)->getQuery()->execute();

        $ids = [];
        foreach ($results as $result) {
            $ids[] = $result->getPersistenceObjectIdentifier();
        }

        $this->assertContains($visible->getPersistenceObjectIdentifier(), $ids);
        $this->assertContains($hidden->getPersistenceObjectIdentifier(), $ids);
    }

    public function testGeneratesLinkToNodeContainingList(): void
    {
        $site = $this->initializeSite('foo-bar');
        $node = $this->initializeNode('/sites/foo-bar', 'live', $site);
        $pageContainingList = $this->initializeNode(
            '/sites/foo-bar/node-123456',
            'live',
            $site,
            [ 'uriPathSegment' => 'foo-bar-here-is-the-detail-page' ]
        );

        $filter = $this->getRoot(
            ArticleConstants::TYPE_TOUR,
            [],
            $node,
            [
                'default' => [
                    'lists' => [
                        ArticleConstants::TYPE_TOUR => 'node://' . $pageContainingList->getIdentifier(),
                    ],

                ],
            ]
        );

        $context = $this->objectManager
            ->get(ControllerContextFactory::class)
            ->initializeFakeControllerContext($node);
        $uri = $filter->getListUri($context, [], null);
        $this->assertContains('foo-bar-here-is-the-detail-page', $uri);
    }

    /** @dataProvider provideArticleTypesForPreselectedCategories */
    public function testAppendsPreselectedCategoriesToUrlIfLinkingToNode($type): void
    {
        $category = (new CategoryFactory($this->objectManager))->create();
        (new ArticleFactory($this->objectManager))->create([ 'mainType' => $type, 'categories' => [ $category ] ]);

        $site = $this->initializeSite('foo-bar');
        $node = $this->initializeNode('/sites/foo-bar', 'live', $site);
        $pageContainingList = $this->initializeNode(
            '/sites/foo-bar/node-123456',
            'live',
            $site,
            [ 'uriPathSegment' => 'foo-bar-here-is-the-detail-page' ]
        );

        $filter = $this->getRoot(
            $type,
            [],
            $node,
            [
                'default' => [
                    'lists' => [
                        $type => 'node://' . $pageContainingList->getIdentifier(),
                    ],

                ],
            ]
        );

        $context = $this->objectManager
            ->get(ControllerContextFactory::class)
            ->initializeFakeControllerContext($node);
        $uri = $filter->getListUri(
            $context,
            [ 'preselectedCategories' => [ $category->getPersistenceObjectIdentifier() ] ],
            null
        );
        $this->assertContains(
            sprintf('categories[0]=%s', $category->getPersistenceObjectIdentifier()),
            urldecode($uri)
        );
    }

    /** @dataProvider providePreselectedProperties */
    public function testAppendsPreselectedPropertiesToUrlIfLinkingToNode(
        int $type,
        string $property,
        string $expectedUriSegment
    ): void {
        $site = $this->initializeSite('foo-bar');
        $node = $this->initializeNode('/sites/foo-bar', 'live', $site);
        $pageContainingList = $this->initializeNode(
            '/sites/foo-bar/node-123456',
            'live',
            $site,
            [ 'uriPathSegment' => 'foo-bar-here-is-the-detail-page' ]
        );

        $filter = $this->getRoot(
            $type,
            [],
            $node,
            [
                'default' => [
                    'lists' => [
                        $type => 'node://' . $pageContainingList->getIdentifier(),
                    ],

                ],
            ]
        );
        $context = $this->objectManager
            ->get(ControllerContextFactory::class)
            ->initializeFakeControllerContext($node);
        $uri = $filter->getListUri($context, [ 'preselectedProperties' => [ $property ] ], null);
        $this->assertContains($expectedUriSegment, urldecode($uri));
    }


    /** @dataProvider provideArticleTypesForPreselectedCities */
    public function testAppendsPreselectedCitiesToUrlIfLinkingToNode($type): void
    {
        $city = (new ArticleFactory($this->objectManager))->create([ 'mainType' => ArticleConstants::TYPE_CITY ]);

        $site = $this->initializeSite('foo-bar');
        $node = $this->initializeNode('/sites/foo-bar', 'live', $site);
        $pageContainingList = $this->initializeNode(
            '/sites/foo-bar/node-123456',
            'live',
            $site,
            [ 'uriPathSegment' => 'foo-bar-here-is-the-detail-page' ]
        );

        $filter = $this->getRoot(
            $type,
            [],
            $node,
            [
                'default' => [
                    'lists' => [
                        $type => 'node://' . $pageContainingList->getIdentifier(),
                    ],

                ],
            ]
        );
        $context = $this->objectManager
            ->get(ControllerContextFactory::class)
            ->initializeFakeControllerContext($node);
        $uri = $filter->getListUri($context, [ 'preselectedCities' => [ $city->getPersistenceObjectIdentifier() ] ], null);
        $this->assertContains(sprintf('city[0]=%s', $city->getPersistenceObjectIdentifier()), urldecode($uri));
    }


    /** @dataProvider provideArticleTypesForPreselectedTags */
    public function testAppendsPreselectedTagsToUrlIfLinkingToNode($type): void
    {
        $tag = (new AttributeFactory($this->objectManager))->create([ 'name' => 'tag', 'value' => 'foobarbaz' ]);
        (new ArticleFactory($this->objectManager))->create([ 'mainType' => $type, 'attributes' => [ $tag ] ]);

        $site = $this->initializeSite('foo-bar');
        $node = $this->initializeNode('/sites/foo-bar', 'live', $site);
        $pageContainingList = $this->initializeNode(
            '/sites/foo-bar/node-123456',
            'live',
            $site,
            [ 'uriPathSegment' => 'foo-bar-here-is-the-detail-page' ]
        );

        $filter = $this->getRoot(
            $type,
            [],
            $node,
            [
                'default' => [
                    'lists' => [
                        $type => 'node://' . $pageContainingList->getIdentifier(),
                    ],

                ],
            ]
        );
        $context = $this->objectManager
            ->get(ControllerContextFactory::class)
            ->initializeFakeControllerContext($node);
        $uri = $filter->getListUri($context, [ 'preselectedTags' => [ $tag->getPersistenceObjectIdentifier() ] ], null);
        $this->assertContains(sprintf('tags[0]=%s', $tag->getPersistenceObjectIdentifier()), urldecode($uri));
    }


    private function getRoot(
        int $articleType = null,
        array $clientFilterConfiguration = null,
        NodeInterface $node = null,
        array $listLinkTargets = []
    ): ArticleRoot {
        if ($node !== null && $articleType !== null) {
            $root = (new ArticleFilterFactory($node))->createFilterForArticleType($articleType, []);
        } else {
            $root = new ArticleRoot([], $node);
        }

        if ($articleType !== null) {
            $root->setArticleType($articleType);
        }

        if ($clientFilterConfiguration !== null) {
            $clientFilterService = new ArticleClientFilterService();
            $this->inject($clientFilterService, 'configuration', $clientFilterConfiguration);
            $this->inject($root, 'clientFilterService', $clientFilterService);
        }

        $linkListService = new LinkListService();
        $this->inject($linkListService, 'listLinkTargets', $listLinkTargets);
        $this->inject($root, 'linkListService', $linkListService);

        return $root;
    }

    public function provideArticleTypesForPreselectedCategories(): array
    {
        return [
            'TYPE_ATTRACTION' => [ ArticleConstants::TYPE_ATTRACTION ],
            'TYPE_GASTRONOMY' => [ ArticleConstants::TYPE_GASTRONOMY ],
            'TYPE_TOUR' => [ ArticleConstants::TYPE_TOUR ],

            // The types below do not support preselected categories
//            'TYPE_LODGING' => [ ArticleConstants::TYPE_LODGING ],
//            'TYPE_DIRECT_MARKETER' => [ ArticleConstants::TYPE_DIRECT_MARKETER ],
//            'TYPE_CONGRESS_LOCATION' => [ ArticleConstants::TYPE_CONGRESS_LOCATION ],
//            'TYPE_CITY' => [ ArticleConstants::TYPE_CITY ],
        ];
    }

    public function providePreselectedProperties(): array
    {
        return [
            'TYPE_GASTRONOMY' => [
                ArticleConstants::TYPE_GASTRONOMY,
                'gastronomyStyle:Regional',
                'gastronomyStyle[0]=regional',
            ],
            'TYPE_TOUR' => [ ArticleConstants::TYPE_TOUR, 'properties:oneWayTour', 'tourType[0]=oneWayTour' ],
            'TYPE_DIRECT_MARKETER' => [
                ArticleConstants::TYPE_DIRECT_MARKETER,
                'shopping:Auf dem Hof',
                'purchase[0]=atTheFarm',
            ],

            // The types below do not support preselectedProperties
//            'TYPE_ATTRACTION' => [ ArticleConstants::TYPE_ATTRACTION ],
//            'TYPE_LODGING' => [ ArticleConstants::TYPE_LODGING ],
//            'TYPE_CONGRESS_LOCATION' => [ ArticleConstants::TYPE_CONGRESS_LOCATION ],
//            'TYPE_CITY' => [ ArticleConstants::TYPE_CITY ],
        ];
    }

    public function provideArticleTypesForPreselectedCities(): array
    {
        return [
            'TYPE_GASTRONOMY' => [ ArticleConstants::TYPE_GASTRONOMY ],
            'TYPE_TOUR' => [ ArticleConstants::TYPE_TOUR ],
            'TYPE_DIRECT_MARKETER' => [ ArticleConstants::TYPE_DIRECT_MARKETER ],
            'TYPE_ATTRACTION' => [ ArticleConstants::TYPE_ATTRACTION ],

            // The following types do not support preselected cities
//            'TYPE_CONGRESS_LOCATION' => [ ArticleConstants::TYPE_CONGRESS_LOCATION ],
//            'TYPE_LODGING' => [ ArticleConstants::TYPE_LODGING ],
//            'TYPE_CITY' => [ ArticleConstants::TYPE_CITY ],
        ];
    }

    public function provideArticleTypesForPreselectedTags(): array
    {
        return [
            'TYPE_ATTRACTION' => [ ArticleConstants::TYPE_ATTRACTION ],

            // The following types do not support preselected cities
//            'TYPE_GASTRONOMY' => [ ArticleConstants::TYPE_GASTRONOMY ],
//            'TYPE_TOUR' => [ ArticleConstants::TYPE_TOUR ],
//            'TYPE_CONGRESS_LOCATION' => [ ArticleConstants::TYPE_CONGRESS_LOCATION ],
//            'TYPE_LODGING' => [ ArticleConstants::TYPE_LODGING ],
//            'TYPE_CITY' => [ ArticleConstants::TYPE_CITY ],
//            'TYPE_DIRECT_MARKETER' => [ ArticleConstants::TYPE_DIRECT_MARKETER ],
        ];
    }

}
