<?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\Address;
use Newland\Toubiz\Sync\Neos\Domain\Repository\AddressRepository;
use Newland\Toubiz\Sync\Neos\Importer\AddressImporter;
use Newland\Toubiz\Sync\Neos\Orm\Uuid\UuidGenerator;
use Newland\Toubiz\Sync\Neos\Tests\Factory\AddressFactory;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\AddressAdapterMock;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\GeoLocationFactoryMock;
use Newland\Toubiz\Sync\Neos\Tests\Unit\Importer\Mock\GeoLocationServiceMock;

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

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

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

    public function setUp(): void
    {
        parent::setUp();
        $this->subject = $this->objectManager->get(AddressImporter::class);
        $this->factory = new AddressFactory($this->objectManager);
    }

    public function testReusesGivenModel(): void
    {
        /** @var Address $address */
        $address = $this->factory->create(
            [
                'originalId' => 'original-id-123',
            ]
        );

        $adapter = new AddressAdapterMock(
            [
                'title' => '__FOO__',
                'externalId' => 'original-id-123',
            ]
        );

        $test = $this->subject->import($adapter);

        $fromDb = $this->persistenceManager->getObjectByIdentifier(
            $address->getPersistenceObjectIdentifier(),
            Address::class
        );
        $this->assertNotNull($fromDb);
        $this->assertEquals('__FOO__', $fromDb->getName());
        $this->assertEquals('__FOO__', $address->getName());
    }


    public function testCreatesNewAddressIfNoneGiven(): void
    {
        $adapter = new AddressAdapterMock([ 'title' => '__BAR__' ]);
        $imported = $this->subject->import($adapter);

        $fromDb = $this->persistenceManager->getObjectByIdentifier(
            $imported->getPersistenceObjectIdentifier(),
            Address::class
        );
        $this->assertNotNull($fromDb);
        $this->assertEquals('__BAR__', $fromDb->getName());
    }

    public function testTwoAddressesWithSameExternalIdAreNotOverwritten(): void
    {
        $this->subject->setLanguage('de');
        $one = $this->subject->import(new AddressAdapterMock(['externalId' => 'test' ]));

        $this->persistenceManager->persistAll();

        $two = $this->subject->import(new AddressAdapterMock(['externalId' => 'test' ]));

        $this->assertNotNull($one);
        $this->assertNotNull($two);
        $this->assertEquals($one->getPersistenceObjectIdentifier(), $two->getPersistenceObjectIdentifier());
    }

    public function testResolvesZipCodeFromDatabaseIfLookupIsIndicatedByAdapter(): void
    {
        $this->initializeGeoLocationService('12345');
        $address = $this->subject->import(new AddressAdapterMock([ 'isLookup' => true ]));
        $this->assertEquals('12345', $address->getZip());
    }

    public function testDoesNotResolveZipCodeFromDatabaseIfAdapterProvidesAZipCodeItself(): void
    {
        $this->initializeGeoLocationService('12345');
        $address = $this->subject->import(new AddressAdapterMock([ 'zip' => '456789', 'isLookup' => true ]));
        $this->assertEquals('456789', $address->getZip());
    }

    public function testDoesNotResolveZipCodeFromDatabaseIfAddressInDatabaseAlreadyHas(): void
    {
        $this->subject->setLanguage('de');
        $existing = $this->factory->create([
            'language' => 'de',
            'latitude' => 12.123,
            'longitude' => 23.345,
            'zip' => '12345',
            'originalId' => 'original-id-444'
        ]);
        $adapter = new AddressAdapterMock([
            'externalId' => 'original-id-444',
            'isLookup' => true,
            'latitude' => 12.123,
            'longitude' => 23.345,
      ]);
        $this->assertEquals('12345', $existing->getZip());

        $this->initializeGeoLocationService('456789');
        $imported = $this->subject->import($adapter);

        $this->assertEquals($existing->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());
        $this->assertEquals('12345', $imported->getZip());
    }

    public function testDoesResolveZipCodeFromDatabaseIfAddressInDatabaseAlreadyHasButCoordinatesHaveChangedSinceLastImport(): void
    {
        $this->subject->setLanguage('de');
        $existing = $this->factory->create([
           'language' => 'de',
           'latitude' => 12.123,
           'longitude' => 23.345,
           'zip' => '12345',
           'originalId' => 'original-id-444'
       ]);
        $adapter = new AddressAdapterMock([
            'externalId' => 'original-id-444',
            'isLookup' => true,
            'latitude' => 99.9999,
            'longitude' => 99.9999,
        ]);
        $this->assertEquals('12345', $existing->getZip());

        $this->initializeGeoLocationService('456789');
        $imported = $this->subject->import($adapter);

        $this->assertEquals($existing->getPersistenceObjectIdentifier(), $imported->getPersistenceObjectIdentifier());
        $this->assertEquals('456789', $imported->getZip());

    }

    private function initializeGeoLocationService(string $zipToGenerate): void
    {
        $service = new GeoLocationServiceMock($zipToGenerate);
        $factory = new GeoLocationFactoryMock($service);
        $this->subject->injectGeoLocationService($factory);
    }
}
