<?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\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Domain\Repository\ArticleRepository;
use Newland\Toubiz\Sync\Neos\Importer\AddressImporter;
use Newland\Toubiz\Sync\Neos\Importer\ArticleImporter;
use Newland\Toubiz\Sync\Neos\Orm\Uuid\UuidGenerator;
use Newland\Toubiz\Sync\Neos\Tests\Factory\AddressFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\ArticleFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\FileFactory;
use Newland\Toubiz\Sync\Neos\Tests\Factory\MediaFactory;
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\FileAdapterMock;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\MediaAdapterMock;

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);
    }

    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()[0]->getPersistenceObjectIdentifier(),
            $english->getAddresses()[0]->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()[0]->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()[0]->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()[0]->getPersistenceObjectIdentifier(),
            $english->getFiles()[0]->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()[0]->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()[0]->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()[0]->getPersistenceObjectIdentifier(),
            $english->getMedia()[0]->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()[0]->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()[0]->getPersistenceObjectIdentifier(),
            $media->getPersistenceObjectIdentifier()
        );
    }

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

        $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 provideEmptyClients(): array
    {
        return [
            [ null ],
            [ '' ],
            [ 'default' ],
        ];
    }

}
