<?php declare(strict_types=1);

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

use Neos\Flow\Tests\FunctionalTestCase;
use Newland\NeosTestingHelpers\InteractsWithNodes;
use Newland\Toubiz\Api\ObjectAdapter\Concern\ArticleConstants;
use Newland\Toubiz\Poi\Neos\Filter\ArticleFilterFactory;
use Newland\Toubiz\Poi\Neos\Filter\Items\ArticleRoot;
use Newland\Toubiz\Poi\Neos\Filter\Items\CityZipList;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Tests\Factory\AddressFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\ArticleFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\CityDataFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\ZipCodeFactory;

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

    /** @var ArticleFilterFactory */
    protected $filterFactory;

    /** @var ArticleFactory */
    protected $articleFactory;

    /** @var CityDataFactory */
    protected $cityDataFactory;

    /** @var AddressFactory */
    protected $addressFactory;

    public function setUp(): void
    {
        parent::setUp();
        $this->filterFactory = new ArticleFilterFactory($this->initializeNode('/sites/foo-bar'));
        $this->cityDataFactory = new CityDataFactory($this->objectManager);
        $this->articleFactory = new ArticleFactory($this->objectManager);
        $this->addressFactory = new AddressFactory($this->objectManager);
    }

    public function testFindsArticlesByTheirZipCodes(): void
    {
        $city1 = $this->createCity([ '12345', '55555' ]);
        $city2 = $this->createCity([ '98765', '55555' ]);

        $poi1 = $this->createPoi('12345');
        $poi2 = $this->createPoi('98765');
        $poi3 = $this->createPoi('55555');

        $filter = $this->createFilter('OR', [
            [ 'label' => 'City 1', 'value' => $city1->getPersistenceObjectIdentifier() ],
            [ 'label' => 'City 2', 'value' => $city2->getPersistenceObjectIdentifier() ],
        ]);

        $filter->setState([ 'city' => [ $city1->getPersistenceObjectIdentifier() ] ]);
        $ids = array_map(
            function(Article $article) { return $article->getPersistenceObjectIdentifier(); },
            $filter->getArticleQuery()->getQuery()->execute()
        );

        $this->assertContains($poi1->getPersistenceObjectIdentifier(), $ids, 'Filter for city with zips [12345, 55555] should find poi with zip 12345');
        $this->assertNotContains($poi2->getPersistenceObjectIdentifier(), $ids, 'Filter for city with zips [12345, 55555] should not find poi with zip 98765');
        $this->assertContains($poi3->getPersistenceObjectIdentifier(), $ids, 'Filter for city with zips [12345, 55555] should find poi with zip 55555');
    }

    public function testCombinesCitiesWithOr(): void
    {
        $city1 = $this->createCity([ '12345', '55555' ]);
        $city2 = $this->createCity([ '98765', '55555' ]);

        $poi1 = $this->createPoi('12345');
        $poi2 = $this->createPoi('98765');
        $poi3 = $this->createPoi('55555');
        $poi4 = $this->createPoi('77777');

        $filter = $this->createFilter('OR', [
            [ 'label' => 'City 1', 'value' => $city1->getPersistenceObjectIdentifier() ],
            [ 'label' => 'City 2', 'value' => $city2->getPersistenceObjectIdentifier() ],
        ]);

        $filter->setState([ 'city' => [ $city1->getPersistenceObjectIdentifier(), $city2->getPersistenceObjectIdentifier() ] ]);
        $ids = array_map(
            function(Article $article) { return $article->getPersistenceObjectIdentifier(); },
            $filter->getArticleQuery()->getQuery()->execute()
        );

        $this->assertContains($poi1->getPersistenceObjectIdentifier(), $ids, 'Search for cities with zips [12345,55555] OR [98765, 55555] should find poi with zip 12345');
        $this->assertContains($poi2->getPersistenceObjectIdentifier(), $ids, 'Search for cities with zips [12345,55555] OR [98765, 55555] should find poi with zip 98765');
        $this->assertContains($poi3->getPersistenceObjectIdentifier(), $ids, 'Search for cities with zips [12345,55555] OR [98765, 55555] should find poi with zip 55555');
        $this->assertNotContains($poi4->getPersistenceObjectIdentifier(), $ids, 'Search for cities with zips [12345,55555] OR [98765, 55555] should not find poi with zip 77777');
    }

    private function createCity(array $zipCodes): Article
    {
        $zipFactory = new ZipCodeFactory($this->objectManager);
        $zips = array_map([ $zipFactory, 'findOrCreate' ], $zipCodes);
        return $this->articleFactory->create([
             'mainType' => ArticleConstants::TYPE_CITY,
             'cityData' => $this->cityDataFactory->make([ 'zipCodes' => $zips ]),
         ]);
    }

    private function createPoi(string $zip): Article
    {
        return $this->articleFactory->create([
             'mainType' => ArticleConstants::TYPE_ATTRACTION,
             'mainAddress' => $this->addressFactory->make([ 'zip' => $zip ])
         ]);
    }

    private function createFilter(string $combine, array $options): ArticleRoot
    {
        return  $this->filterFactory->createFilter([
           'combine' => 'AND',
           'join' => [ [ 'column' => 'entity.mainAddress', 'alias' => 'mainAddress' ] ],
           'children' => [
               'city' => [
                   'type' => CityZipList::class,
                   'combine' => $combine,
                   'databaseColumn' => 'mainAddress.zip',
                   'queryString' => 'city',
                   'dataSource' => 'newland-neosfiltering-tests-example',
                   'dataSourceArguments' => $options
               ]
           ]
       ]);
    }
}
