<?php declare(strict_types=1);

namespace Newland\Toubiz\Poi\Neos\Tests\Integration\Lists;

use Newland\Toubiz\Api\ObjectAdapter\ArticleAdapterInterface;
use Newland\Toubiz\Api\ObjectAdapter\Attributes\TourAttributes;
use Newland\Toubiz\Api\ObjectAdapter\Concern\ArticleConstants;
use Newland\Toubiz\Api\Service\GeoLocation\NullService;
use Newland\Toubiz\Api\Service\Toubiz\Legacy\DbService\CategoryMap;
use Newland\Toubiz\Api\Service\Toubiz\Legacy\ObjectAdapter\CityApiService\CityAdapter;
use Newland\Toubiz\Sync\Neos\Importer\ArticleImporter;
use Newland\Toubiz\Sync\Neos\Service\GeoLocationFactory;
use Newland\Toubiz\Sync\Neos\Service\UuidPredictionService;
use Newland\Toubiz\Sync\Neos\Tests\Factory\AddressFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\CityDataFactory;
use Newland\Toubiz\Api\Service\Outdooractive\ObjectAdapter\TourAdapter as OutdooractiveTourAdapter;
use Newland\Toubiz\Api\Service\Toubiz\Legacy\ObjectAdapter\TourApiService\TourAdapter as ToubizTourAdapter;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\GeoLocationFactoryMock;
use Psr\Log\NullLogger;

class TourListTest extends AbstractListTestCase
{

    public function setUp(): void
    {
        parent::setUp();
        $this->objectManager->setInstance(
            GeoLocationFactory::class,
            new GeoLocationFactoryMock(new NullService())
        );
    }

    public function tearDown(): void
    {
        $this->objectManager->forgetInstance(GeoLocationFactory::class);
        parent::tearDown();
    }

    protected function articleType(): int
    {
        return ArticleConstants::TYPE_TOUR;
    }

    public function testFiltersForCitiesAlongTour(): void
    {
        [ $firstCity, $secondCity ] =
            $this->articleFactory->createMultiple(2, [ 'mainType' => ArticleConstants::TYPE_CITY ]);

        $this->articleFactory->create(
            [ 'name' => '__TOUR_FIRST__', 'mainType' => $this->articleType(), 'cities' => [ $firstCity ] ]
        );
        $this->articleFactory->create(
            [ 'name' => '__TOUR_SECOND__', 'mainType' => $this->articleType(), 'cities' => [ $secondCity ] ]
        );
        $this->articleFactory->create(
            [ 'name' => '__TOUR_BOTH__', 'mainType' => $this->articleType(), 'cities' => [ $firstCity, $secondCity ] ]
        );
        $this->articleFactory->create(
            [ 'name' => '__TOUR_NONE__', 'mainType' => $this->articleType(), 'cities' => [] ]
        );

        // No filter
        $response = $this->browser->request($this->nodeUri());
        $this->assertResponseOk($response);
        $this->assertResponseContains('__TOUR_FIRST__', $response);
        $this->assertResponseContains('__TOUR_SECOND__', $response);
        $this->assertResponseContains('__TOUR_BOTH__', $response);
        $this->assertResponseContains('__TOUR_NONE__', $response);

        // Single filter
        $response =
            $this->browser->request($this->nodeUri([ 'city[0]' => $firstCity->getPersistenceObjectIdentifier() ]));
        $this->assertResponseOk($response);
        $this->assertResponseContains('__TOUR_FIRST__', $response);
        $this->assertResponseNotContains('__TOUR_SECOND__', $response);
        $this->assertResponseContains('__TOUR_BOTH__', $response);
        $this->assertResponseNotContains('__TOUR_NONE__', $response);

        // Two filters
        $response = $this->browser->request(
            $this->nodeUri(
                [
                    'city[0]' => $firstCity->getPersistenceObjectIdentifier(),
                    'city[1]' => $secondCity->getPersistenceObjectIdentifier()
                ]
            )
        );
        $this->assertResponseContains('__TOUR_FIRST__', $response);
        $this->assertResponseContains('__TOUR_SECOND__', $response);
        $this->assertResponseContains('__TOUR_BOTH__', $response);
        $this->assertResponseNotContains('__TOUR_NONE__', $response);
    }

    public function testDoesNotFilterForCityOfMainAddress(): void
    {
        $city = $this->articleFactory->create(
            [
                'mainType' => ArticleConstants::TYPE_CITY,
                'cityData' => (new CityDataFactory($this->objectManager))->make([ 'zipCodes' => [ '45678' ] ])
            ]
        );
        $this->articleFactory->create(
            [
                'mainType' => $this->articleType(),
                'name' => '__FIRST__',
                'mainAddress' => (new AddressFactory($this->objectManager))->make([ 'zip' => '45678' ])
            ]
        );
        $this->articleFactory->create(
            [
                'mainType' => $this->articleType(),
                'name' => '__SECOND__',
                'mainAddress' => (new AddressFactory($this->objectManager))->make([ 'zip' => '12345' ])
            ]
        );

        // No filter
        $response = $this->browser->request($this->nodeUri());
        $this->assertResponseOk($response);
        $this->assertResponseContains('__FIRST__', $response);
        $this->assertResponseContains('__SECOND__', $response);

        // Filter query string.
        // Nothing should be found since both created tours are not actually related to the cities
        // but only share a common zip code in their address.
        $response = $this->browser->request($this->nodeUri([ 'city[0]' => $city->getPersistenceObjectIdentifier() ]));
        $this->assertResponseOk($response);
        $this->assertResponseNotContains('__FIRST__', $response);
        $this->assertResponseNotContains('__SECOND__', $response);
    }

    /** @dataProvider provideAdaptersForCities */
    public function testToubizToursWithCitiesCanBeFilteredForAfterImporting(CityAdapter $cityAdapter, ArticleAdapterInterface $tourAdapter): void
    {
        /** @var ArticleImporter $importer */
        $importer = $this->objectManager->get(ArticleImporter::class);
        $importer->setUuidPredictionService(new UuidPredictionService());
        $city = $importer->import($cityAdapter);
        $tour = $importer->import($tourAdapter);
        $differentCity = $this->articleFactory->create([ 'mainType' => ArticleConstants::TYPE_CITY ]);

        // Some of the tour data has names that are html-encoded in the template making
        // it harder to test for it. Therefor the name is overridden.
        $name = '__TOUR__' . md5(random_bytes(32)) . '__';
        $tour->setName($name);
        $this->persistenceManager->update($tour);
        $this->persistenceManager->persistAll();

        // Filter for city
        $response = $this->browser->request($this->nodeUri([ 'city[0]' => $city->getPersistenceObjectIdentifier() ]));
        $this->assertResponseOk($response);
        $this->assertResponseContains($name, $response);

        // Filter for different city
        $response = $this->browser->request($this->nodeUri([ 'city[0]' => $differentCity->getPersistenceObjectIdentifier() ]));
        $this->assertResponseOk($response);
        $this->assertResponseNotContains($name, $response);
    }

    public function provideAdaptersForCities(): array
    {
        $data = [];

        $toubizToursCity = new CityAdapter(json_decode(file_get_contents(__DIR__ . '/data/toubizTour/city.json'), true));
        foreach (glob(__DIR__ . '/data/toubizTour/tour*') as $file) {
            $tourAdapter = new ToubizTourAdapter(json_decode(file_get_contents($file), true));
            $tourAdapter->setLogger(new NullLogger());
            $tourAdapter->setCategoryMap(new CategoryMap());
            $data[basename($file)] = [ $toubizToursCity, $tourAdapter ];
        }

        $outdooractiveCity = New CityAdapter(json_decode(file_get_contents(__DIR__ . '/data/outdooractive/city.json'), true));
        foreach (glob(__DIR__ . '/data/outdooractive/tour*') as $file) {
            $tourAdapter = new OutdooractiveTourAdapter(json_decode(file_get_contents($file), true));
            $data[basename($file)] = [ $outdooractiveCity, $tourAdapter ];
        }

        return $data;
    }


    public function testAddsPageSpecificOutdoorActiveApiConfiguration(): void
    {
        $config = [
            'default' => [  'apiKey' => '__DEFAULT_API_KEY__', 'client' => '__DEFAULT_CLIENT__' ],
            'sites' => [ $this->site->getNodeName() => [  'apiKey' => '__SPECIFIC_API_KEY__', 'client' => '__SPECIFIC_CLIENT__' ] ],
        ];
        $this->withMockedConfiguration(
            [ 'Newland.Toubiz.Poi.Neos.outdoorActiveConfigurations' => $config ],
            function() {
                $response = $this->browser->request($this->nodeUri());
                $this->assertResponseOk($response);
                $this->assertResponseContains('data-outdooractive-api-config.api-key="__SPECIFIC_API_KEY__"', $response);
                $this->assertResponseContains('data-outdooractive-api-config.client="__SPECIFIC_CLIENT__"', $response);
            }
        );
    }

    public function testAddsDefaultOutdoorActiveApiConfigurationIfNoSpecificExists(): void
    {
        $config = [
            'default' => [  'apiKey' => '__DEFAULT_API_KEY__', 'client' => '__DEFAULT_CLIENT__' ],
        ];
        $this->withMockedConfiguration(
            [ 'Newland.Toubiz.Poi.Neos.outdoorActiveConfigurations' => $config ],
            function() {
                $response = $this->browser->request($this->nodeUri());
                $this->assertResponseOk($response);
                $this->assertResponseContains('data-outdooractive-api-config.api-key="__DEFAULT_API_KEY__"', $response);
                $this->assertResponseContains('data-outdooractive-api-config.client="__DEFAULT_CLIENT__"', $response);
            }
        );
    }

    public function testListDoesContainNonEmptyDistances(): void
    {
        $this->articleFactory->create([ 'mainType' => 2, 'attributes' => [
            $this->attributeFactory->create([ 'name' => TourAttributes::TOUR_DURATION, 'data' => 92 ])
        ] ]);

        $response = $this->browser->request($this->nodeUri());
        $this->assertResponseOk($response);
        $this->assertResponseContains('01:32', $response);
    }

    public function testListDoesNotContainEmptyDistances(): void
    {
        $this->articleFactory->create([ 'mainType' => 2, 'attributes' => [
            $this->attributeFactory->create([ 'name' => TourAttributes::TOUR_DURATION, 'data' => 0 ])
        ] ]);

        $response = $this->browser->request($this->nodeUri());
        $this->assertResponseOk($response);
        $this->assertResponseNotContains('00:00', $response);
    }


    public function testAjaxSplitScreenListDoesContainNonEmptyDistances(): void
    {
        $this->articleFactory->create([ 'mainType' => 2, 'attributes' => [
            $this->attributeFactory->create([ 'name' => TourAttributes::TOUR_DURATION, 'data' => 92 ])
        ] ]);

        $url = sprintf(
            'http://localhost/toubiz-poi/filtered-list?node=%s&format=ajax&page=1&layout=list',
            $this->node->getContextPath()
        );

        $response = $this->browser->request($url);
        $this->assertResponseOk($response);
        $this->assertResponseContains('01:32', $response);
    }


    public function testAjaxSplitScreenListDoesNotContainEmptyDistances(): void
    {
        $this->articleFactory->create([ 'mainType' => 2, 'attributes' => [
            $this->attributeFactory->create([ 'name' => TourAttributes::TOUR_DURATION, 'data' => 0 ])
        ] ]);

        $url = sprintf(
            'http://localhost/toubiz-poi/filtered-list?node=%s&format=ajax&page=1&layout=list',
            $this->node->getContextPath()
        );

        $response = $this->browser->request($url);
        $this->assertResponseOk($response);
        $this->assertResponseNotContains('00:00', $response);
    }

}
