<?php declare(strict_types=1);

namespace Newland\Toubiz\Sync\Neos\Tests\Unit\Domain\Repository;
use Doctrine\Common\Collections\ArrayCollection;
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\Tests\Factory\ArticleFactory;
use Newland\Toubiz\Api\ObjectAdapter\Concern\ArticleConstants;
use Newland\Toubiz\Sync\Neos\Domain\Filter\ArticleFilter;
use Newland\Toubiz\Sync\Neos\Tests\Factory\CategoryFactory;

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

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

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

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


        $categoryFactory = new CategoryFactory($this->objectManager);
        $categoryOne = $categoryFactory->create();
        $categoryTwo = $categoryFactory->create();
        $categoryThree = $categoryFactory->create();

        $articleFactory = new ArticleFactory($this->objectManager);
        $articleFactory->create();
        $articleFactory->create(
            [
                'categories' => new ArrayCollection(
                    [
                        $categoryOne,
                        $categoryTwo,
                        $categoryThree,
                    ]
                ),
            ]
        );

        $this->subject = $this->objectManager->get(ArticleRepository::class);
        $this->subject->setStrictLanguageHandling(false);

    }

    public function testFindOneByOriginalIdAndClientRespectsLanguages(): void
    {
        $german = $this->factory->create([ 'language' => 'de', 'client' => 'foo', 'originalId' => 'bar' ]);
        $english = $this->factory->create([ 'language' => 'en', 'client' => 'foo', 'originalId' => 'bar' ]);

        $this->subject->setLanguage('de');
        $result = $this->subject->findOneByOriginalIdAndClient('bar', 'foo');
        $this->assertNotNull($result);
        $this->assertEquals($german->getPersistenceObjectIdentifier(), $result->getPersistenceObjectIdentifier());

        $this->subject->setLanguage('en');
        $result = $this->subject->findOneByOriginalIdAndClient('bar', 'foo');
        $this->assertNotNull($result);
        $this->assertEquals($english->getPersistenceObjectIdentifier(), $result->getPersistenceObjectIdentifier());
    }

    /** @dataProvider provideEmptyClients */
    public function testFindOneByOriginalIdAndClientIgnoresEmptyClientValues($client)
    {
        $this->subject->setLanguage('de');
        $article = $this->factory->create([ 'language' => 'de', 'originalId' => 'bar' ]);
        $article->setClient($client);
        $this->objectManager->get(EntityManagerInterface::class)->persist($article);
        $this->objectManager->get(EntityManagerInterface::class)->flush($article);

        $result = $this->subject->findOneByOriginalIdAndClient('bar', 'someclientstring');
        $this->assertNotNull($result);
        $this->assertEquals($article->getPersistenceObjectIdentifier(), $result->getPersistenceObjectIdentifier());
    }

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

        $result = $this->subject->findOneByOriginalIdAndClient('bar', 'sometestclient');
        $this->assertNotNull($result);
        $this->assertEquals($clientMatch->getPersistenceObjectIdentifier(), $result->getPersistenceObjectIdentifier());
    }

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


    public function testQueryForOldRecordsRespectsLanguages(): void
    {
        $german = $this->factory->create([ 'language' => 'de', 'updatedAt' => new \DateTime('2019-01-01T13:00:00') ]);
        $english = $this->factory->create([ 'language' => 'en', 'updatedAt' => new \DateTime('2019-01-01T13:00:00') ]);

        $this->subject->setLanguage('de');
        $result = $this->subject->queryForOldRecords(
            new \DateInterval('PT2H'),
            null,
            new \DateTimeImmutable('2019-01-01T19:00:00')
        )->delete()->getQuery()->execute();

        $this->assertEquals(1, $result);
        $this->subject->setLanguage('de');
        $germanFromDatabase = $this->subject->findByIdentifier($german->getPersistenceObjectIdentifier());
        $this->assertTrue($germanFromDatabase === null, 'German should have been deleted');

        $this->subject->setLanguage('en');
        $englishFromDatabase = $this->subject->findByIdentifier($english->getPersistenceObjectIdentifier());
        $this->assertNotNull($englishFromDatabase, 'English should have not been deleted');
    }

    public function testOrphanFindingRespectsLanguages(): void
    {
        // Both are made orphans by having no client
        $german = $this->factory->create([ 'language' => 'de' ]);
        $german->setClient(null);
        $this->subject->update($german);
        $english = $this->factory->create([ 'language' => 'en']);
        $english->setClient(null);
        $this->subject->update($english);
        $this->persistenceManager->persistAll();

        $this->subject->setLanguage('de');
        $result = $this->subject->orphanQuery()->getQuery()->execute();
        $ids = array_map(
            function(Article $article) {
                return $article->getPersistenceObjectIdentifier();
            },
            (array) $result
        );

        $this->assertContains($german->getPersistenceObjectIdentifier(), $ids, 'Should have found german');
        $this->assertNotContains($english->getPersistenceObjectIdentifier(), $ids, 'Should not have found english');
    }

    public function testRemoveByIdsShouldRespectLanguages(): void
    {
        $german = $this->factory->create([ 'language' => 'de' ]);
        $english = $this->factory->create([ 'language' => 'en' ]);

        $this->subject->setLanguage('en');
        $this->subject->removeByIds([ $german->getPersistenceObjectIdentifier(), $english->getPersistenceObjectIdentifier() ]);

        $this->subject->setLanguage('de');
        $germanFromDatabase = $this->subject->findByIdentifier($german->getPersistenceObjectIdentifier());
        $this->assertNotNull($germanFromDatabase, 'German article should not have been deleted');

        $this->subject->setLanguage('en');
        $englishFromDatabase = $this->subject->findByIdentifier($english->getPersistenceObjectIdentifier());
        $this->assertNull($englishFromDatabase, 'English article should have been deleted');
    }


    public function testFilterFindsArticlesWithNoCategory(): void
    {
        $result = $this->subject->countByFilter(new ArticleFilter(), 10);
        $this->assertEquals(2, $result['items']);
    }

    public function testDataSourceListsOneOptionForEveryCategoryAndOneIfNoCategoryAttached(): void
    {
        $this->assertCount(4, $this->subject->findAllForDataSource(new ArticleFilter()));
    }

    /**
     * @dataProvider provideArticleTypes
     * @param int $type
     */
    public function testEmptyFilterFindsArticlesOfAllTypes(int $type): void
    {
        $article = (new ArticleFactory($this->objectManager))->create([ 'mainType' => $type ]);
        $ids = array_map(
            function(Article $article) { return $article->getPersistenceObjectIdentifier(); },
            $this->subject->findByFilter(new ArticleFilter())
        );
        $this->assertContains($article->getPersistenceObjectIdentifier(), $ids);
    }


    /**
     * @dataProvider provideArticleTypes
     * @param int $type
     */
    public function testFilterForArticleTypeFindsArticlesOfThatType(int $type): void
    {
        $article = (new ArticleFactory($this->objectManager))->create([ 'mainType' => $type ]);

        $filter = new ArticleFilter();
        $filter->setMainType($type);
        $ids = array_map(
            function(Article $article) { return $article->getPersistenceObjectIdentifier(); },
            $this->subject->findByFilter($filter)
        );
        $this->assertContains($article->getPersistenceObjectIdentifier(), $ids);
    }

    public function provideArticleTypes(): array
    {
        return array_map(
            function(int $type) { return [ $type ]; },
            ArticleConstants::ALL_TYPES
        );
    }
}
