<?php declare(strict_types=1);

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

use Neos\Flow\Tests\FunctionalTestCase;
use Newland\Toubiz\Sync\Neos\Domain\Model\Article;
use Newland\Toubiz\Sync\Neos\Importer\ArticleImporter;
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(
    )
    {
        $german = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => '__FOO__',
                    'language' => 'de',
                    'mainAddress' => new AddressAdapterMock(
                        [
                            'externalId' => '__BAR__',
                        ]
                    ),
                ]
            )
        );
        $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->assertNotEquals(
            $german->getMainAddress()->getPersistenceObjectIdentifier(),
            $english->getMainAddress()->getPersistenceObjectIdentifier(),
            'Main Addresses of german and english should be different models'
        );

    }

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

        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    'language' => $article->getLanguage(),
                    '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([]), new AddressAdapterMock([]) ] ]
            )
        );

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


    public function testImportingSameArticleInMultipleLanguagesWithAddressesUsingSameOriginalIdDoesNotOverwriteAddresses(
    )
    {
        $german = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => '__FOO__',
                    'language' => 'de',
                    'addresses' => [
                        new AddressAdapterMock(
                            [
                                'originalId' => '__BAR__',
                            ]
                        ),
                    ],
                ]
            )
        );
        $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();
        $article = (new ArticleFactory($this->objectManager))->create([ 'addresses' => [ $address ] ]);

        $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();
        $article = (new ArticleFactory($this->objectManager))->create([ 'addresses' => [ $address ] ]);

        $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 ] ]);

        $imported = $this->subject->import(
            new ArticleAdapterMock(
                [
                    'externalId' => $article->getOriginalId(),
                    '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 ] ]);

        $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 ] ]);

        $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 ] ]);

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

}
