<?php declare(strict_types=1);

namespace Newland\Toubiz\Sync\Neos\Tests\Unit\Importer;

use Doctrine\ORM\EntityManagerInterface;
use Neos\Flow\Tests\FunctionalTestCase;
use Newland\Toubiz\Api\ObjectAdapter\Article\ExternalIdSelector;
use Newland\Toubiz\Api\ObjectAdapter\Concern\ArticleConstants;
use Newland\Toubiz\Api\ObjectAdapter\Concern\ExternalIdType;
use Newland\Toubiz\Api\ObjectAdapter\ExternalIdAdapter;
use Newland\Toubiz\Api\ObjectAdapter\HasLanguageGroupingSeparateFromOriginalId;
use Newland\Toubiz\Api\Service\Toubiz\Legacy\ObjectAdapter\CityApiService\CityAdapter;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;
use Newland\Toubiz\Sync\Neos\Domain\Repository\RecordConfigurationRepository;
use Newland\Toubiz\Sync\Neos\Importer\AddressImporter;
use Newland\Toubiz\Sync\Neos\Importer\ArticleImporter;
use Newland\Toubiz\Sync\Neos\Service\UuidPredictionService;
use Newland\Toubiz\Sync\Neos\Tests\Factory\AddressFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\ArticleFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\AttributeFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\AwardFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\CategoryFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\FileFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\MediaFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\StarClassificationFactory;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\AddressAdapterMock;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\ArticleAdapterMock;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\CategoryAdapterMock;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\FileAdapterMock;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\MediaAdapterMock;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\StarClassificationAdapterMock;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\UriAdapterMock;

class ArticleImporterTest extends FunctionalTestCase
{
    protected static $testablePersistenceEnabled = true;

    /** @var ArticleImporter */
    protected $subject;

    public function setUp(): void
    {
        parent::setUp();
        $this->subject = $this->objectManager->get(ArticleImporter::class);
        $this->inject(
            $this->subject,
            'packageConfig',
            [
                'downloadImages' => [
                    '-1' => false,
                    'cities' => false,
                    'poi' => false,
                    'gastronomy' => false,
                    'lodgings' => false,
                    'tours' => false,
                    'directMarketers' => false,
                    'congressLocations' => false,
                ],
            ]
        );
    }

    public function testImportsMainAddress(): void
    {
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [ 'mainAddress' => new AddressAdapterMock([]) ]
            )
        );

        $fromDb = $this->persistenceManager->getObjectByIdentifier(
            $imported->getPersistenceObjectIdentifier(),
            Article::class
        );
        $this->assertNotNull($fromDb->getMainAddress(), 'Article should have main address');
    }


    public function testImportingSameArticleInMultipleLanguagesWithMainAddressUsingSameOriginalIdDoesNotOverwriteAddresses(
    ): void
    {
        $this->subject->setLanguage('de');
        /**
         * @var AddressImporter $addressImporter
         * */
        $addressImporter = $this->objectManager->get(AddressImporter::class);
        $addressImporter->setLanguage('de');
        $german = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => '__FOO__',
                    'language' => 'de',
                    'mainAddress' => new AddressAdapterMock(
                        [
                            'externalId' => '__BAR__',
                        ]
                    ),
                ]
            )
        );
        $this->subject->setLanguage('en');

        $english = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => '__FOO__',
                    'language' => 'en',
                    'mainAddress' => new AddressAdapterMock(
                        [
                            'externalId' => '__BAR__',
                        ]
                    ),
                ]
            )
        );

        $this->assertNotEquals(
            $german->getPersistenceObjectIdentifier(),
            $english->getPersistenceObjectIdentifier(),
            'German and english articles should have different ids'
        );
        $this->assertNotNull($german->getMainAddress(), 'German should have main address');
        $this->assertNotNull($english->getMainAddress(), 'English should have main address');
        $this->assertEquals(
            $german->getLanguage(),
            $german->getMainAddress()->getLanguage(),
            'German article should have a German address.'
        );
        $this->assertEquals(
            $english->getLanguage(),
            $english->getMainAddress()->getLanguage(),
            'English article should have an English address.'
        );
        $this->assertNotEquals(
            $german->getMainAddress()->getPersistenceObjectIdentifier(),
            $english->getMainAddress()->getPersistenceObjectIdentifier(),
            'Main Addresses of german and english should be different models'
        );
    }

    public function testImportingMultipleArticlesInOneLanguageWithMainAddressUsesSameAddress(): void
    {
        $address = (new AddressFactory($this->objectManager))->create(
            [ 'originalId' => '__FOO__', 'language' => 'de' ]
        );


        $articleOne = (new ArticleFactory($this->objectManager))->create(
            [ 'language' => 'de', 'mainAddress' => $address ]
        );
        $faultyAddress = (new AddressFactory($this->objectManager))->create(
            [ 'originalId' => '__FOO__', 'language' => 'en' ]
        );
        $articleTwo = (new ArticleFactory($this->objectManager))->create(
            [ 'language' => 'de', 'mainAddress' => $faultyAddress ]
        );

        $this->assertEquals(
            $articleOne->getMainAddress()->getOriginalId(),
            $articleTwo->getMainAddress()->getOriginalId(),
            'Both article addresses should have the same original id.'
        );

        $this->assertNotEquals(
            $articleOne->getMainAddress()->getPersistenceObjectIdentifier(),
            $articleTwo->getMainAddress()->getPersistenceObjectIdentifier(),
            'Both article addresses are expected to not have the same address record before import.'
        );

        $importedOne = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $articleOne->getOriginalId(),
                    'language' => 'de',
                    'mainAddress' => new AddressAdapterMock(
                        [
                            'externalId' => '__FOO__',
                        ]
                    ),
                ]
            )
        );

        $importedTwo = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $articleTwo->getOriginalId(),
                    'language' => 'de',
                    'mainAddress' => new AddressAdapterMock(
                        [
                            'externalId' => '__FOO__',
                        ]
                    ),
                ]
            )
        );

        $this->assertEquals(
            $importedOne->getMainAddress()->getPersistenceObjectIdentifier(),
            $importedTwo->getMainAddress()->getPersistenceObjectIdentifier(),
            'Both articles should share the same address record.'
        );
    }

    public function testImportingMainAddressReusesExistingModel(): void
    {
        $address = (new AddressFactory($this->objectManager))
            ->create([ 'language' => 'de', 'originalId' => '__BAR__' ]);
        $article = (new ArticleFactory($this->objectManager))
            ->create([ 'mainAddress' => $address, 'language' => 'de' ]);

        $this->subject->setClient($article->getClient());
        $this->subject->setLanguage($article->getLanguage());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => $article->getLanguage(),
                    'mainType' => $article->getMainType(),
                    'mainAddress' => new AddressAdapterMock(
                        [
                            'externalId' => '__BAR__',
                        ]
                    ),
                ]
            )
        );

        $this->assertEquals($article->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());
        $this->assertNotNull($imported->getMainAddress());
        $this->assertEquals(
            $imported->getMainAddress()->getPersistenceObjectIdentifier(),
            $address->getPersistenceObjectIdentifier()
        );
    }


    public function testImportsAddresses(): void
    {
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'addresses' =>
                        [
                            new AddressAdapterMock([ 'externalId' => 'test1' ]),
                            new AddressAdapterMock([ 'externalId' => 'test2' ]),
                        ],
                ]
            )
        );

        $fromDb = $this->persistenceManager->getObjectByIdentifier(
            $imported->getPersistenceObjectIdentifier(),
            Article::class
        );

        $this->assertEquals(2, $fromDb->getAddresses()->count(), 'Article should have 2 addresses');
    }


    public function testImportingSameArticleInMultipleLanguagesWithAddressesUsingSameOriginalIdDoesNotOverwriteAddresses(
    )
    {
        $this->subject->setClient('FOO_CLIENT');
        $this->subject->setLanguage('de');
        $german = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => '__FOO__',
                    'language' => 'de',
                    'addresses' => [
                        new AddressAdapterMock(
                            [
                                'externalId' => '__BAR__',
                            ]
                        ),
                    ],
                ]
            )
        );
        $this->subject->setLanguage('en');
        $english = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => '__FOO__',
                    'language' => 'en',
                    'addresses' => [
                        new AddressAdapterMock(
                            [
                                'externalId' => '__BAR__',
                            ]
                        ),
                    ],
                ]
            )
        );

        $this->assertNotEquals(
            $german->getPersistenceObjectIdentifier(),
            $english->getPersistenceObjectIdentifier(),
            'German and english articles should have different ids'
        );
        $this->assertEquals(1, $german->getAddresses()->count(), 'German should have one address');
        $this->assertEquals(1, $english->getAddresses()->count(), 'English should have one address');
        $this->assertNotEquals(
            $german->getAddresses()->first()->getPersistenceObjectIdentifier(),
            $english->getAddresses()->first()->getPersistenceObjectIdentifier(),
            'Main Addresses of german and english should be different models'
        );

    }

    public function testImportingAddressesReusesExistingModelIfOriginalIdMatches(): void
    {
        $address = (new AddressFactory($this->objectManager))->create([ 'language' => 'de' ]);
        $article =
            (new ArticleFactory($this->objectManager))->create([ 'addresses' => [ $address ], 'language' => 'de' ]);

        $this->subject->setClient($article->getClient());
        $this->subject->setLanguage($article->getLanguage());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => $article->getLanguage(),
                    'addresses' => [
                        new AddressAdapterMock(
                            [
                                'externalId' => $address->getOriginalId(),
                            ]
                        ),
                    ],
                ]
            )
        );

        $this->assertEquals($article->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());
        $this->assertEquals(1, $imported->getAddresses()->count());
        $this->assertEquals(
            $imported->getAddresses()->first()->getPersistenceObjectIdentifier(),
            $address->getPersistenceObjectIdentifier()
        );
    }

    public function testImportingAddressesDoesNotReuseExistingModelIfOriginalIdDoesNotMatch(): void
    {
        $address = (new AddressFactory($this->objectManager))->create([ 'language' => 'de' ]);
        $article =
            (new ArticleFactory($this->objectManager))->create([ 'addresses' => [ $address ], 'language' => 'de' ]);

        $this->subject->setClient($article->getClient());
        $this->subject->setLanguage($article->getLanguage());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => $article->getLanguage(),
                    'addresses' => [
                        new AddressAdapterMock(
                            [
                                'externalId' => '__FOOBAR__',
                            ]
                        ),
                    ],
                ]
            )
        );

        $this->assertEquals($article->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());
        $this->assertEquals(1, $imported->getAddresses()->count());
        $this->assertNotEquals(
            $imported->getAddresses()->first()->getPersistenceObjectIdentifier(),
            $address->getPersistenceObjectIdentifier()
        );
    }


    public function testImportsFiles(): void
    {
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [ 'files' => [ new FileAdapterMock([]), new FileAdapterMock([]) ] ]
            )
        );

        $fromDb = $this->persistenceManager->getObjectByIdentifier(
            $imported->getPersistenceObjectIdentifier(),
            Article::class
        );
        $this->assertEquals(2, $fromDb->getFiles()->count(), 'Article should have 2 files');
    }


    public function testImportingSameArticleInMultipleLanguagesWithFilesUsingSameOriginalIdDoesNotOverwriteAddresses()
    {
        $german = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => '__FOO__',
                    'language' => 'de',
                    'files' => [
                        new FileAdapterMock(
                            [
                                'originalId' => '__BAR__',
                            ]
                        ),
                    ],
                ]
            )
        );
        $english = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => '__FOO__',
                    'language' => 'en',
                    'files' => [
                        new FileAdapterMock(
                            [
                                'externalId' => '__BAR__',
                            ]
                        ),
                    ],
                ]
            )
        );

        $this->assertNotEquals(
            $german->getPersistenceObjectIdentifier(),
            $english->getPersistenceObjectIdentifier(),
            'German and english articles should have different ids'
        );
        $this->assertEquals(1, $german->getFiles()->count(), 'German should have one file');
        $this->assertEquals(1, $english->getFiles()->count(), 'English should have one file');
        $this->assertNotEquals(
            $german->getFiles()->first()->getPersistenceObjectIdentifier(),
            $english->getFiles()->first()->getPersistenceObjectIdentifier(),
            'files of german and english should be different models'
        );

    }

    public function testImportingFilesReusesExistingModelIfOriginalIdMatches(): void
    {
        $file = (new FileFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create([ 'files' => [ $file ] ]);

        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'mainType' => $article->getMainType(),
                    'language' => $article->getLanguage(),
                    'files' => [
                        new FileAdapterMock(
                            [
                                'externalId' => $file->getOriginalId(),
                            ]
                        ),
                    ],
                ]
            )
        );

        $this->assertEquals($article->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());
        $this->assertEquals(1, $imported->getFiles()->count());
        $this->assertEquals(
            $imported->getFiles()->first()->getPersistenceObjectIdentifier(),
            $file->getPersistenceObjectIdentifier()
        );
    }

    public function testImportingFilesDoesNotReuseExistingModelIfOriginalIdDoesNotMatch(): void
    {
        $file = (new FileFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create([ 'files' => [ $file ] ]);
        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => $article->getLanguage(),
                    'files' => [
                        new FileAdapterMock(
                            [
                                'externalId' => '__FOOBAR__',
                            ]
                        ),
                    ],
                ]
            )
        );

        $this->assertEquals($article->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());
        $this->assertEquals(1, $imported->getFiles()->count());
        $this->assertNotEquals(
            $imported->getFiles()->first()->getPersistenceObjectIdentifier(),
            $file->getPersistenceObjectIdentifier()
        );
    }


    public function testImportsMedia(): void
    {
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'media' => [
                        new MediaAdapterMock([ 'externalId' => 'foo' ]),
                        new MediaAdapterMock([ 'externalId' => 'bar' ]),
                    ],
                ]
            )
        );

        $fromDb = $this->persistenceManager->getObjectByIdentifier(
            $imported->getPersistenceObjectIdentifier(),
            Article::class
        );
        $this->assertEquals(2, $fromDb->getMedia()->count(), 'Article should have 2 media items');
    }

    public function testImportingSameArticleInMultipleLanguagesWithMediaUsingSameOriginalIdDoesNotOverwriteAddresses()
    {
        $german = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => '__FOO__',
                    'language' => 'de',
                    'media' => [
                        new MediaAdapterMock(
                            [
                                'originalId' => '__BAR__',
                            ]
                        ),
                    ],
                ]
            )
        );
        $english = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => '__FOO__',
                    'language' => 'en',
                    'media' => [
                        new MediaAdapterMock(
                            [
                                'externalId' => '__BAR__',
                            ]
                        ),
                    ],
                ]
            )
        );

        $this->assertNotEquals(
            $german->getPersistenceObjectIdentifier(),
            $english->getPersistenceObjectIdentifier(),
            'German and english articles should have different ids'
        );
        $this->assertEquals(1, $german->getMedia()->count(), 'German should have one media item');
        $this->assertEquals(1, $english->getMedia()->count(), 'English should have one media item');
        $this->assertNotEquals(
            $german->getMedia()->first()->getPersistenceObjectIdentifier(),
            $english->getMedia()->first()->getPersistenceObjectIdentifier(),
            'media items of german and english should be different models'
        );

    }

    public function testImportingMediaReusesExistingModelIfOriginalIdMatches(): void
    {
        $media = (new MediaFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create([ 'media' => [ $media ] ]);
        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => $article->getLanguage(),
                    'media' => [
                        new MediaAdapterMock(
                            [
                                'externalId' => $media->getOriginalId(),
                            ]
                        ),
                    ],
                ]
            )
        );

        $this->assertEquals($article->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());
        $this->assertEquals(1, $imported->getMedia()->count());
        $this->assertEquals(
            $imported->getMedia()->first()->getPersistenceObjectIdentifier(),
            $media->getPersistenceObjectIdentifier()
        );
    }

    public function testImportingMediaDoesNotReuseExistingModelIfOriginalIdDoesNotMatch(): void
    {
        $media = (new MediaFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create([ 'media' => [ $media ] ]);
        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => $article->getLanguage(),
                    'media' => [
                        new MediaAdapterMock(
                            [
                                'externalId' => '__FOOBAR__',
                            ]
                        ),
                    ],
                ]
            )
        );

        $this->assertEquals($article->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());
        $this->assertEquals(1, $imported->getMedia()->count());
        $this->assertNotEquals(
            $imported->getMedia()->first()->getPersistenceObjectIdentifier(),
            $media->getPersistenceObjectIdentifier()
        );
    }

    public function testImportsGeoLocation(): void
    {
        $article = (new ArticleFactory($this->objectManager))->create([]);
        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => $article->getLanguage(),
                    'latitude' => 55,
                    'longitude' => 32,
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());

        $fromDb = $this->persistenceManager->getObjectByIdentifier(
            $article->getPersistenceObjectIdentifier(),
            Article::class
        );
        $this->assertNotNull($fromDb);
        $this->assertEquals(55, $fromDb->getLatitude());
        $this->assertEquals(32, $fromDb->getLongitude());
    }

    public function testDoesNotUpdateIdentifierWhenImporting(): void
    {
        $article = (new ArticleFactory($this->objectManager))->create(
            [ 'persistenceObjectIdentifier' => '23a06f38-4a54-42d2-8807-52a160572a9a' ]
        );
        $id = $article->getPersistenceObjectIdentifier();
        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'name' => 'test',
                ]
            )
        );

        $this->assertSame($id, $imported->getPersistenceObjectIdentifier());
        $this->assertSame(
            1,
            $this->objectManager
                ->get(ArticleRepository::class)
                ->countAll()
        );
    }

    /** @dataProvider provideEmptyClients */
    public function testDoesNotUpdateIdentifierWhenImportingWithEmptyClient($client): void
    {
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'persistenceObjectIdentifier' => '23a06f38-4a54-42d2-8807-52a160572a9a',
            ]
        );
        $article->setClient($client);
        $this->objectManager->get(EntityManagerInterface::class)->persist($article);
        $this->objectManager->get(EntityManagerInterface::class)->flush($article);
        $id = $article->getPersistenceObjectIdentifier();

        $this->subject->setClient('foobar');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'name' => 'test',
                ]
            )
        );

        $this->assertSame($id, $imported->getPersistenceObjectIdentifier());
        $this->assertSame(
            1,
            $this->objectManager
                ->get(ArticleRepository::class)
                ->countAll()
        );
    }

    /** @dataProvider provideEmptyClients */
    public function testReusesArticleWithEmptyClientAndUpdatesTheClient($client): void
    {
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'persistenceObjectIdentifier' => '23a06f38-4a54-42d2-8807-52a160572a9a',
            ]
        );
        $article->setClient($client);
        $this->objectManager->get(EntityManagerInterface::class)->persist($article);
        $this->objectManager->get(EntityManagerInterface::class)->flush($article);
        $id = $article->getPersistenceObjectIdentifier();

        $this->subject->setClient('foobar');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'name' => 'test',
                ]
            )
        );

        $this->assertSame($id, $imported->getPersistenceObjectIdentifier());
        $this->assertSame(
            1,
            $this->objectManager->get(ArticleRepository::class)->countAll()
        );
        $this->assertEquals('foobar', $imported->getClient());
    }

    /** @dataProvider provideEmptyClients */
    public function testReusesObjectWithSameClientIfMultipleArticlesExistWithEmptyValues($client): void
    {
        $clientMatch =
            (new ArticleFactory($this->objectManager))->create([ 'originalId' => 'bar', 'client' => 'sometestclient' ]);
        $withEmptyClient = (new ArticleFactory($this->objectManager))->create([ 'originalId' => 'bar' ]);
        $withEmptyClient->setClient($client);
        $this->objectManager->get(EntityManagerInterface::class)->persist($withEmptyClient);
        $this->objectManager->get(EntityManagerInterface::class)->flush($withEmptyClient);

        $this->subject->setClient('sometestclient');
        $imported = $this->subject->import(new ArticleAdapterMock([ 'externalId' => 'bar', ]));

        $this->assertSame(
            2,
            $this->objectManager->get(ArticleRepository::class)->countAll()
        );
        $this->assertEquals(
            $clientMatch->getPersistenceObjectIdentifier(),
            $imported->getPersistenceObjectIdentifier()
        );
    }

    public function testCanSaveProblematicPayloadFromHopf(): void
    {
        $this->expectNotToPerformAssertions();
        $data = json_decode(
            file_get_contents(__DIR__ . '/../../Fixture/problematic-city-sample-hopf-TBPOI-468.json'),
            true
        );
        $adapter = new CityAdapter($data);

        $this->subject->setUuidPredictionService(new UuidPredictionService());
        $this->subject->setLanguage('de');
        $this->subject->setClient('test');
        $this->subject->import($adapter);
        $this->objectManager->get(EntityManagerInterface::class)->flush();
        $this->subject->import($adapter);
        $this->objectManager->get(EntityManagerInterface::class)->flush();
    }


    public function testTryingToImportExistingMediaOnArticleDoesNotTryToDuplicateIt(): void
    {
        $media = (new MediaFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'media' => [ $media ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $media) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_article_media_join WHERE neos_article = ? AND neos_medium = ?',
                [ $article->getPersistenceObjectIdentifier(), $media->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'media' => [
                        new MediaAdapterMock([ 'externalId' => $media->getOriginalId() ]),
                        new MediaAdapterMock([ 'externalId' => $media->getOriginalId() ]),
                    ],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();
        $this->assertEquals(1, $countRelationRecords());
    }

    public function testDisassociatesMediaFromArticleIfNotInData(): void
    {
        $media = (new MediaFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'media' => [ $media ],
                'client' => 'test',
                'language' => 'de',
            ]
        );
        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $media) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_article_media_join WHERE neos_article = ? AND neos_medium = ?',
                [ $article->getPersistenceObjectIdentifier(), $media->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'media' => [],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();

        $this->assertEquals(0, $countRelationRecords());
    }

    public function testMediaModelIsReused(): void
    {
        $media = (new MediaFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'media' => [ $media ],
                'client' => 'test',
                'language' => 'de',
            ]
        );
        $mediaId = $media->getPersistenceObjectIdentifier();

        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'media' => [
                        new MediaAdapterMock([ 'externalId' => $media->getOriginalId() ]),
                    ],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());

        $this->assertEquals(
            $mediaId,
            $imported->getMedia()->first()->getPersistenceObjectIdentifier()
        );
    }

    public function testTryingToImportExistingStarClassificationOnArticleDoesNotTryToDuplicateIt(): void
    {
        $classification = (new StarClassificationFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'starClassifications' => [ $classification ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $classification) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain__0d687_starclassifications_join WHERE neos_article = ? AND neos_starclassification = ?',
                [ $article->getPersistenceObjectIdentifier(), $classification->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'starRatings' => [
                        new StarClassificationAdapterMock([ 'externalId' => $classification->getOriginalId() ]),
                        new StarClassificationAdapterMock([ 'externalId' => $classification->getOriginalId() ]),
                    ],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();
        $this->assertEquals(1, $countRelationRecords());
    }

    public function testDisassociatesStarClassificationFromArticleIfNotInData(): void
    {
        $classification = (new StarClassificationFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'starClassifications' => [ $classification ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $classification) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain__0d687_starclassifications_join WHERE neos_article = ? AND neos_starclassification = ?',
                [ $article->getPersistenceObjectIdentifier(), $classification->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'starRatings' => [],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();

        $this->assertEquals(0, $countRelationRecords());
    }

    public function testDisassociatesAwardFromArticleIfNotInData(): void
    {
        $award = (new AwardFactory($this->objectManager))->create(
            [ 'language' => 'de' ]
        );
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'awardValues' => [ $award ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = static function () use ($em, $article, $award) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_award WHERE article = ?',
                [ $article->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        self::assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $this->subject->setLanguage('de');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'awardValues' => [],
                ]
            )
        );
        self::assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();

        self::assertEquals(0, $countRelationRecords());
    }

    public function testTryingToImportExistingAddressOnArticleDoesNotTryToDuplicateIt(): void
    {
        $address = (new AddressFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'addresses' => [ $address ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $address) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_article_addresses_join WHERE neos_article = ? AND neos_address = ?',
                [ $article->getPersistenceObjectIdentifier(), $address->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'addresses' => [
                        new AddressAdapterMock([ 'externalId' => $address->getOriginalId() ]),
                        new AddressAdapterMock([ 'externalId' => $address->getOriginalId() ]),
                    ],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();
        $this->assertEquals(1, $countRelationRecords());
    }

    public function testDisassociatesStarAddressFromArticleIfNotInData(): void
    {
        $address = (new AddressFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'addresses' => [ $address ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $address) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_article_addresses_join WHERE neos_article = ? AND neos_address = ?',
                [ $article->getPersistenceObjectIdentifier(), $address->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'addresses' => [],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();

        $this->assertEquals(0, $countRelationRecords());
    }

    public function testTryingToImportExistingCategoryOnArticleDoesNotTryToDuplicateIt(): void
    {
        $category = (new CategoryFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'categories' => [ $category ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $category) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_article_categories_join WHERE neos_article = ? AND neos_category = ?',
                [ $article->getPersistenceObjectIdentifier(), $category->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'categories' => [
                        new CategoryAdapterMock(
                            [ 'externalId' => $category->getOriginalId(), 'name' => $category->getTitle() ]
                        ),
                        new CategoryAdapterMock(
                            [ 'externalId' => $category->getOriginalId(), 'name' => $category->getTitle() ]
                        ),
                    ],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();
        $this->assertEquals(1, $countRelationRecords());
    }

    public function testDisassociatesCategoryFromArticleIfNotInData(): void
    {

        $category = (new CategoryFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'categories' => [ $category ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $category) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_article_categories_join WHERE neos_article = ? AND neos_category = ?',
                [ $article->getPersistenceObjectIdentifier(), $category->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'categories' => [],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();

        $this->assertEquals(0, $countRelationRecords());
    }

    public function testTryingToImportExistingFileOnArticleDoesNotTryToDuplicateIt(): void
    {
        $file = (new FileFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'files' => [ $file ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $file) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_article_files_join WHERE neos_article = ? AND neos_file = ?',
                [ $article->getPersistenceObjectIdentifier(), $file->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'files' => [
                        new FileAdapterMock([ 'externalId' => $file->getOriginalId() ]),
                        new FileAdapterMock([ 'externalId' => $file->getOriginalId() ]),
                    ],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();
        $this->assertEquals(1, $countRelationRecords());
    }

    public function testDisassociatesFileFromArticleIfNotInData(): void
    {
        $file = (new FileFactory($this->objectManager))->create();
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'files' => [ $file ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $file) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_article_files_join WHERE neos_article = ? AND neos_file = ?',
                [ $article->getPersistenceObjectIdentifier(), $file->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'files' => [],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();

        $this->assertEquals(0, $countRelationRecords());
    }

    public function testDisassociatesAttributeFromArticleIfNotInData(): void
    {
        $attribute = (new AttributeFactory($this->objectManager))->create([ 'name' => 'foo', 'data' => 'bar' ]);
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'attributes' => [ $attribute ],
                'client' => 'test',
                'language' => 'de',
            ]
        );

        /** @var EntityManagerInterface $em */
        $em = $this->objectManager->get(EntityManagerInterface::class);
        $countRelationRecords = function () use ($em, $article, $attribute) {
            return (int) $em->getConnection()->executeQuery(
                'SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_attribute WHERE article = ? AND persistence_object_identifier = ?',
                [ $article->getPersistenceObjectIdentifier(), $attribute->getPersistenceObjectIdentifier() ]
            )->fetchColumn();
        };

        $this->assertEquals(1, $countRelationRecords());
        $this->subject->setClient('test');
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => 'de',
                    'attributes' => [],
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $em->flush();

        $this->assertEquals(0, $countRelationRecords());
    }

    public function testSavesSortingOfCategoriesCorrectlyEvenIfNothingChanged(): void
    {
        $article = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => 'foobar',
                    'language' => 'de',
                    'categories' => [
                        new CategoryAdapterMock([ 'externalId' => 'foo', 'name' => 'foo' ]),
                        new CategoryAdapterMock([ 'externalId' => 'bar', 'name' => 'bar' ]),
                    ],
                ]
            )
        );

        $this->persistenceManager->persistAll();
        $article = $this->persistenceManager->getObjectByIdentifier(
            $article->getPersistenceObjectIdentifier(),
            Article::class
        );
        $this->assertEquals('foo', $article->getCategoriesSorted()[0]->getOriginalId());
        $this->assertEquals('bar', $article->getCategoriesSorted()[1]->getOriginalId());

        $article = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => 'foobar',
                    'language' => 'de',
                    'categories' => [
                        new CategoryAdapterMock([ 'externalId' => 'bar', 'name' => 'bar' ]),
                        new CategoryAdapterMock([ 'externalId' => 'foo', 'name' => 'foo' ]),
                    ],
                ]
            )
        );

        $this->persistenceManager->persistAll();
        $article = $this->persistenceManager->getObjectByIdentifier(
            $article->getPersistenceObjectIdentifier(),
            Article::class
        );
        $this->assertEquals('bar', $article->getCategoriesSorted()[0]->getOriginalId());
        $this->assertEquals('foo', $article->getCategoriesSorted()[1]->getOriginalId());
    }

    public function provideEmptyClients(): array
    {
        return [
            [ null ],
            [ '' ],
            [ 'default' ],
        ];
    }

    public function testImportsExternalIds(): void
    {
        $article = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => 'foobar',
                    'language' => 'de',
                    'additionalExternalIds' => [
                        new ExternalIdAdapter('first', 'some_id'),
                        new ExternalIdAdapter('second', 'some_other_id'),
                    ],
                ]
            )
        );

        /** @var Article $article */
        $article = $this->persistenceManager->getObjectByIdentifier(
            $article->getPersistenceObjectIdentifier(),
            Article::class
        );

        $externalIds = [];
        foreach ($article->getExternalIds() as $externalId) {
            $externalIds[$externalId->getType()] = $externalId->getId();
        }

        $this->assertCount(2, $externalIds);
        $this->assertArrayHasKey('first', $externalIds);
        $this->assertEquals('some_id', $externalIds['first']);
        $this->assertArrayHasKey('second', $externalIds);
        $this->assertEquals('some_other_id', $externalIds['second']);
    }

    public function testAssociatesCitiesBySelector(): void
    {
        $city = (new ArticleFactory($this->objectManager))->create(
            [
                'externalIds' => [
                    [ 'type' => ExternalIdType::TOUBIZ_LEGACY, 'id' => 'test' ],
                ],
            ]
        );

        $article = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => 'foobar',
                    'language' => 'de',
                    'citySelectors' => [
                        new ExternalIdSelector(ExternalIdType::TOUBIZ_LEGACY, 'test'),
                    ],
                ]
            )
        );

        /** @var Article $article */
        $article = $this->persistenceManager->getObjectByIdentifier(
            $article->getPersistenceObjectIdentifier(),
            Article::class
        );

        $this->assertCount(1, $article->getCities());
        $this->assertEquals(
            $city->getPersistenceObjectIdentifier(),
            $article->getCities()->first()->getPersistenceObjectIdentifier()
        );
    }

    public function testImportsBookingUris(): void
    {
        $article = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => 'foobar',
                    'language' => 'de',
                    'bookingUris' => [
                        new UriAdapterMock([ 'externalId' => 'foobar', 'uri' => 'https://test.com' ]),
                        new UriAdapterMock(
                            [ 'externalId' => 'foobar2', 'uri' => 'https://test.com', 'label' => 'test' ]
                        ),
                    ],
                ]
            )
        );

        /** @var Article $article */
        $article = $this->persistenceManager->getObjectByIdentifier(
            $article->getPersistenceObjectIdentifier(),
            Article::class
        );

        $this->assertCount(2, $article->getBookingUris());
        $this->assertEquals('https://test.com', $article->getBookingUris()[0]->getUri());
        $this->assertEquals(null, $article->getBookingUris()[0]->getLabel());
        $this->assertEquals('test', $article->getBookingUris()[1]->getLabel());
    }

    public function testPreservesHiddenStateOfExistingRecords(): void
    {
        $article = (new ArticleFactory($this->objectManager))->create([]);
        $this->assertFalse($article->isHidden());
        $this->objectManager->get(RecordConfigurationRepository::class)->updateConfiguration(
            $article,
            function (array $config) {
                $config['hidden'] = true;
                return $config;
            },
            true
        );

        $article = $this->persistenceManager->getObjectByIdentifier(
            $article->getPersistenceObjectIdentifier(),
            Article::class
        );
        $this->assertTrue($article->isHidden());

        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock([ 'externalId' => $article->getOriginalId(), 'name' => 'FOO' ])
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());

        $article = $this->persistenceManager->getObjectByIdentifier(
            $article->getPersistenceObjectIdentifier(),
            Article::class
        );
        $this->assertTrue($article->isHidden());
    }

    public function testAppliesHiddenStateOfPreviouslyDeletedRecords(): void
    {
        $article = (new ArticleFactory($this->objectManager))->create([]);
        $id = $article->getPersistenceObjectIdentifier();
        $this->objectManager->get(RecordConfigurationRepository::class)->updateConfiguration(
            $article,
            function (array $config) {
                $config['hidden'] = true;
                return $config;
            },
            true
        );

        // Simulating calling `article:remove`
        $this->persistenceManager->remove($article);
        $this->persistenceManager->persistAll();

        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [ 'externalId' => $article->getOriginalId(), 'name' => 'FOO', 'mainType' => $article->getMainType() ]
            )
        );
        $this->assertEquals($id, $imported->getPersistenceObjectIdentifier());
        $imported = $this->persistenceManager->getObjectByIdentifier(
            $imported->getPersistenceObjectIdentifier(),
            Article::class
        );

        $this->assertTrue($imported->isHidden());
    }

    public function testCorrectlyUpdatesHiddenRecord(): void
    {
        $article = (new ArticleFactory($this->objectManager))->create([]);
        $this->objectManager->get(RecordConfigurationRepository::class)->updateConfiguration(
            $article,
            function (array $config) {
                $config['hidden'] = true;
                return $config;
            },
            true
        );
        $this->persistenceManager->persistAll();

        // If it did not reuse records, then the importing would throw a 'duplicate primary key' error
        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'name' => 'FOO',
                    'mainType' => $article->getMainType(),
                ]
            )
        );
        $this->assertEquals($imported->getPersistenceObjectIdentifier(), $article->getPersistenceObjectIdentifier());
        $this->persistenceManager->persistAll();

        $imported = $this->persistenceManager->getObjectByIdentifier(
            $imported->getPersistenceObjectIdentifier(),
            Article::class
        );
        $this->assertTrue($imported->isHidden());
    }

    public function testUpdatesRandomSortingField(): void
    {
        $article = (new ArticleFactory($this->objectManager))->create();
        $randomSorting = $article->getRandomSorting();

        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'name' => 'FOO',
                    'mainType' => $article->getMainType(),
                ]
            )
        );
        $this->assertEquals($article->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());

        $this->persistenceManager->persistAll();
        $imported = $this->persistenceManager->getObjectByIdentifier(
            $imported->getPersistenceObjectIdentifier(),
            Article::class
        );

        $this->assertNotEquals($randomSorting, $imported->getRandomSorting());
    }

    public function testUsesOriginalIdAsLanguageGroupingForAdaptersWithoutLanguageGrouping(): void
    {
        $id = md5(random_bytes(32));
        $german = $this->subject->import(new ArticleAdapterMock([ 'language' => 'de', 'externalId' => $id ]));
        $english = $this->subject->import(new ArticleAdapterMock([ 'language' => 'en', 'externalId' => $id ]));
        $this->persistenceManager->persistAll();

        $englishFromDb = $this->objectManager->get(ArticleRepository::class)->translateInto($german, 'en');

        $this->assertNotNull($englishFromDb);
        $this->assertEquals(
            $english->getPersistenceObjectIdentifier(),
            $englishFromDb->getPersistenceObjectIdentifier()
        );
    }

    public function testImportsLanguageGrouping(): void
    {
        $german = $this->subject->import(
            new class([ 'language' => 'de' ]) extends ArticleAdapterMock
                implements HasLanguageGroupingSeparateFromOriginalId {
                public function getLanguageGrouping(): ?string
                {
                    return 'FOO-BAR';
                }
            }
        );
        $english = $this->subject->import(
            new class([ 'language' => 'en' ]) extends ArticleAdapterMock
                implements HasLanguageGroupingSeparateFromOriginalId {
                public function getLanguageGrouping(): ?string
                {
                    return 'FOO-BAR';
                }
            }
        );
        $this->persistenceManager->persistAll();

        $englishFromDb = $this->objectManager->get(ArticleRepository::class)->translateInto($german, 'en');

        $this->assertNotNull($englishFromDb);
        $this->assertEquals(
            $english->getPersistenceObjectIdentifier(),
            $englishFromDb->getPersistenceObjectIdentifier()
        );
    }

    public function testUsesOriginalIdAsLanguageGroupingInBestEffortAssumptionIfNoneAvailable(): void
    {
        $id = md5(random_bytes(32));
        $german = $this->subject->import(
            new class([ 'language' => 'de', 'externalId' => $id ]) extends ArticleAdapterMock
                implements HasLanguageGroupingSeparateFromOriginalId {
                public function getLanguageGrouping(): ?string
                {
                    return null;
                }
            }
        );
        $english = $this->subject->import(
            new class([ 'language' => 'en', 'externalId' => $id ]) extends ArticleAdapterMock
                implements HasLanguageGroupingSeparateFromOriginalId {
                public function getLanguageGrouping(): ?string
                {
                    return null;
                }
            }
        );
        $this->persistenceManager->persistAll();

        $englishFromDb = $this->objectManager->get(ArticleRepository::class)->translateInto($german, 'en');

        $this->assertNotNull($englishFromDb);
        $this->assertEquals(
            $english->getPersistenceObjectIdentifier(),
            $englishFromDb->getPersistenceObjectIdentifier()
        );
    }

    // See TBPOI-513
    public function testRemovesOpeningTimesIfPreviouslyDefinedButNotAnyMore(): void
    {
        $article = (new ArticleFactory($this->objectManager))->create(
            [
                'openingTimesFormat' => 'FOO',
                'openingTimes' => '{ "foo": "bar" }',
            ]
        );
        $this->assertNotNull($article->getOpeningTimesFormat());
        $this->assertNotNull($article->getOpeningTimes());

        $this->subject->setClient($article->getClient());
        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'mainType' => $article->getMainType(),
                    'openingTimes' => null,
                    'openingTimesFormat' => null,
                ]
            )
        );
        $this->assertEquals($article->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());

        $this->assertNull($imported->getOpeningTimesFormat());
        $this->assertNull($imported->getOpeningTimes());
    }

    public function testSingleLocationExternalIdSelectorCanSelectMultipleCities(): void
    {
        $city1 = (new ArticleFactory($this->objectManager))->create(
            [
                'client' => 'foo',
                'originalId' => 'foo_city',
                'mainType' => ArticleConstants::TYPE_CITY,
                'language' => 'de',
                'externalIds' => [
                    [ 'type' => 'BAR_TYPE', 'id' => 'bar_city' ],
                ],
            ]
        );

        $city2 = (new ArticleFactory($this->objectManager))->create(
            [
                'client' => 'bar',
                'originalId' => 'foo_city',
                'mainType' => ArticleConstants::TYPE_CITY,
                'language' => 'de',
                'externalIds' => [
                    [ 'type' => 'BAR_TYPE', 'id' => 'bar_city' ],
                ],
            ]
        );

        $this->assertNotEquals($city1->getPersistenceObjectIdentifier(), $city2->getPersistenceObjectIdentifier());

        $adapter = new ArticleAdapterMock(
            [
                'client' => 'foo',
                'externalId' => 'foo_article',
                'language' => 'de',
                'citySelectors' => [ new ExternalIdSelector('BAR_TYPE', 'bar_city') ],
            ]
        );

        $importer = new ArticleImporter();
        $importer->setClient('foo');
        $importer->setLanguage('de');
        $article = $importer->import($adapter);

        $ids = $article->getCities()->map(
            function (Article $article) {
                return $article->getPersistenceObjectIdentifier();
            }
        );
        $this->assertCount(2, $ids);
        $this->assertContains($city1->getPersistenceObjectIdentifier(), $ids);
        $this->assertContains($city2->getPersistenceObjectIdentifier(), $ids);
    }

    public function testReimportingExternalIdDoesNotLeaveTracesBehind(): void
    {
        $adapter = new ArticleAdapterMock(
            [
                'client' => 'foo',
                'externalId' => 'foo_city',
                'mainType' => ArticleConstants::TYPE_CITY,
                'additionalExternalIds' => [
                    new ExternalIdAdapter('FOO_TYPE', 'foo_city'),
                ],
            ]
        );

        $importer = new ArticleImporter();
        $importer->setClient('foo');

        $article1 = $importer->import($adapter);
        $this->persistenceManager->persistAll();
        $this->assertEquals(1, $this->countExternalIds());

        $article2 = $importer->import($adapter);
        $this->persistenceManager->persistAll();
        $this->assertEquals(1, $this->countExternalIds());

        $this->assertEquals($article1->getPersistenceObjectIdentifier(), $article2->getPersistenceObjectIdentifier());
        $this->assertCount(1, $article1->getExternalIds());
        $this->assertCount(1, $article2->getExternalIds());
    }

    private function countExternalIds(): int
    {
        return (int) $this->objectManager
            ->get(EntityManagerInterface::class)
            ->getConnection()
            ->executeQuery('SELECT COUNT(*) FROM newland_toubiz_sync_neos_domain_model_externalid')
            ->fetchColumn();
    }
}
