<?php declare(strict_types=1);

namespace Newland\Toubiz\Search\Neos\Tests\Unit\Indexer;

use Neos\Flow\Tests\FunctionalTestCase;
use Newland\Contracts\Neos\Search\IndexRecordModification;
use Newland\Contracts\Neos\Search\SearchBackend;
use Newland\Contracts\Neos\Search\SearchRequest;
use Newland\Contracts\Neos\Search\SearchResultCollection;
use Newland\NeosTestingHelpers\InteractsWithNodes;
use Newland\Toubiz\Search\Neos\Indexer\DocumentNodeIndexer;
use Newland\Toubiz\Search\Neos\Progress\NullHandler;

class DocumentNodeIndexerTest extends FunctionalTestCase implements SearchBackend
{
    use InteractsWithNodes;
    protected static $testablePersistenceEnabled = true;

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

    /** @var string */
    protected $source;

    /** @var IndexRecordModification[] */
    protected $modifications;

    public function setUp(): void
    {
        parent::setUp();
        $this->source = null;
        $this->modifications = [ ];
        $this->subject = $this->objectManager->get(DocumentNodeIndexer::class);
        $this->inject($this->subject, 'contentDimensions', [
            'language' => [
                'label' => 'Language',
                'default' => 'de',
                'defaultPreset' => 'de',
                'presets' => [
                    'de' => [
                        'label' => 'Deutsch',
                        'values' => [ 'de' ],
                        'uriSegment' => 'de',
                    ]
                ]
            ]
        ]);
    }

    public function testCreatesOneIndexEntryPerDocumentNode(): void
    {
        $site = $this->initializeSite('foo');
        $this->nodes([
             '/sites/foo' => [ 'nodeType' => 'Neos.NodeTypes:Page' ],
             '/sites/foo/bar' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'properties' => [ 'title' => '/sites/foo/bar' ] ],
             '/sites/foo/bar/some-content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'foo bar content' ] ],
             '/sites/foo/baz' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'properties' => [ 'title' => '/sites/foo/baz' ]  ],
             '/sites/foo/baz/some-content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'foo baz content' ] ],
         ], [
             'workspaceName' => 'live',
             'language' => 'de',
             'site' => $site
        ]);

        $this->subject->index(['/sites/foo'], $this, new NullHandler());
        $this->assertCount(2, $this->modifications);
    }

    public function testUsesSiteNameAsScope(): void
    {
        $siteFoo = $this->initializeSite('foo');
        $siteBar = $this->initializeSite('bar');
        $this->nodes([
             '/sites/foo' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'site' => $siteFoo ],
             '/sites/foo/page' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'site' => $siteFoo ],
             '/sites/foo/page/content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'content' ], 'site' => $siteFoo ],
             '/sites/bar' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'site' => $siteBar ],
             '/sites/bar/page' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'site' => $siteBar ],
             '/sites/bar/page/content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'content' ], 'site' => $siteBar ],
         ], [
             'workspaceName' => 'live',
             'language' => 'de',
         ]);

        $this->subject->index(['/sites/foo', '/sites/bar'], $this, new NullHandler());
        $scopes = [ [] ];
        foreach ($this->modifications as $modification) {
            $scopes[] = $modification->getScopes();
        }
        $scopes = array_merge(...$scopes);

        $this->assertCount(2, $scopes);
        $this->assertContains('/sites/foo', $scopes);
        $this->assertContains('/sites/bar', $scopes);
    }

    public function testDoesNotIndexPageIfNotPassedAsScopeToIndex(): void
    {
        $siteFoo = $this->initializeSite('foo');
        $siteBar = $this->initializeSite('bar');
        $this->nodes([
             '/sites/foo' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'site' => $siteFoo ],
             '/sites/foo/page' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'site' => $siteFoo ],
             '/sites/foo/page/content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'content' ], 'site' => $siteFoo ],
             '/sites/bar' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'site' => $siteBar ],
             '/sites/bar/page' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'site' => $siteBar ],
             '/sites/bar/page/content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'content' ], 'site' => $siteBar ],
         ], [
             'workspaceName' => 'live',
             'language' => 'de',
         ]);

        $this->subject->index([ '/sites/foo' ], $this, new NullHandler());
        $scopes = [ [] ];
        foreach ($this->modifications as $modification) {
            $scopes[] = $modification->getScopes();
        }
        $scopes = array_merge(...$scopes);

        $this->assertCount(1, $scopes);
        $this->assertContains('/sites/foo', $scopes);
        $this->assertNotContains('/sites/bar', $scopes);
    }

    public function testIndexesAllContentsOfPage(): void
    {
        $site = $this->initializeSite('foo');
        $this->nodes([
             '/sites/foo' => [ 'nodeType' => 'Neos.NodeTypes:Page' ],
             '/sites/foo/bar' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'properties' => [ 'title' => '/sites/foo/bar' ] ],
             '/sites/foo/bar/some-content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'foo bar content' ] ],
         ], [
             'workspaceName' => 'live',
             'language' => 'de',
             'site' => $site
         ]);

        $this->subject->index(['/sites/foo'], $this, new NullHandler());
        $this->assertCount(1, $this->modifications);
        $this->assertContains('foo bar content', $this->modifications[0]->getContent());
    }

    public function testDoesNotIndexContentsOfSubpagesAsContentsOfCurrentPage(): void
    {
        $site = $this->initializeSite('foo');
        $this->nodes([
             '/sites/foo' => [ 'nodeType' => 'Neos.NodeTypes:Page' ],
             '/sites/foo/bar' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'properties' => [ 'title' => '/sites/foo/bar' ] ],
             '/sites/foo/bar/some-content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'foo bar content' ] ],
             '/sites/foo/bar/subpage' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'properties' => [ 'title' => '/sites/foo/bar/subpage' ] ],
             '/sites/foo/bar/subpage/some-content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'subpage content' ] ],
         ], [
             'workspaceName' => 'live',
             'language' => 'de',
             'site' => $site
         ]);

        $this->subject->index(['/sites/foo'], $this, new NullHandler());
        $this->assertCount(2, $this->modifications);
        $this->assertContains('foo bar content', $this->modifications[0]->getContent());
        $this->assertNotContains('subpage content', $this->modifications[0]->getContent());
        $this->assertContains('subpage content', $this->modifications[1]->getContent());
        $this->assertNotContains('foo bar content', $this->modifications[1]->getContent());
    }

    public function testDoesNotIncludeNodeTypeInIndexedContentIfIgnoredInConfiguration(): void
    {
        $this->inject($this->subject, 'configuration', [
            'nodeTypes' => [
                'Neos.NodeTypes:Text' => [
                    'index' => false,
                ]
            ]
        ]);

        $site = $this->initializeSite('foo');
        $this->nodes([
             '/sites/foo' => [ 'nodeType' => 'Neos.NodeTypes:Page' ],
             '/sites/foo/bar' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'properties' => [ 'title' => '/sites/foo/bar' ] ],
             '/sites/foo/bar/some-content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'foo bar content '] ],
         ], [
             'workspaceName' => 'live',
             'language' => 'de',
             'site' => $site
         ]);

        $this->subject->index(['/sites/foo'], $this, new NullHandler());
        $this->assertCount(1, $this->modifications);
        $this->assertNotContains('foo bar content', $this->modifications[0]->getContent());
    }

    public function testDoesNotIncludePropertyInIndexedContentIfIgnoredInConfiguration(): void
    {
        $this->inject($this->subject, 'configuration', [
            'nodeTypes' => [
                'Neos.NodeTypes:Text' => [
                    'ignoredProperties' => [ 'foo' ]
                ]
            ]
        ]);

        $site = $this->initializeSite('foo');
        $this->nodes([
             '/sites/foo' => [ 'nodeType' => 'Neos.NodeTypes:Page' ],
             '/sites/foo/bar' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'properties' => [ 'title' => '/sites/foo/bar' ] ],
             '/sites/foo/bar/some-content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'foo' => 'foo content', 'bar' => 'bar content' ] ],
         ], [
             'workspaceName' => 'live',
             'language' => 'de',
             'site' => $site
         ]);

        $this->subject->index(['/sites/foo'], $this, new NullHandler());
        $this->assertCount(1, $this->modifications);
        $this->assertContains('bar content', $this->modifications[0]->getContent());
        $this->assertNotContains('foo content', $this->modifications[0]->getContent());
    }

    public function testDoesNotIncludeIgnoredStringsInSearchIndex():  void
    {

        $this->inject($this->subject, 'configuration', [
            'nodeTypes' => [
                'Neos.NodeTypes:Text' => [
                    'ignoredStrings' => [ 'content' ]
                ]
            ]
        ]);

        $site = $this->initializeSite('foo');
        $this->nodes([
         '/sites/foo' => [ 'nodeType' => 'Neos.NodeTypes:Page' ],
         '/sites/foo/bar' => [ 'nodeType' => 'Neos.NodeTypes:Page', 'properties' => [ 'title' => '/sites/foo/bar' ] ],
         '/sites/foo/bar/some-content' => [ 'nodeType' => 'Neos.NodeTypes:Text', 'properties' => [ 'text' => 'foo bar content' ] ],
     ], [
         'workspaceName' => 'live',
         'language' => 'de',
         'site' => $site
     ]);

        $this->subject->index(['/sites/foo'], $this, new NullHandler());
        $this->assertCount(1, $this->modifications);
        $this->assertContains('foo bar', $this->modifications[0]->getContent());
        $this->assertNotContains('content', $this->modifications[0]->getContent());
    }

    public function initialize(): void
    {
        // Mock implementatino of Indexing backend
    }

    public function setSource(string $source): void
    {
        $this->source = $source;
    }

    public function createOrUpdateIndexEntry(IndexRecordModification $modification): void
    {
        $this->modifications[] = $modification;
    }

    public function afterIndexing(): void
    {
        // Mock implementatino of Indexing backend
    }

    public function deleteObsoleteIndexEntries(array $identifiersToRetain): void
    {
        // Mock implementatino of Indexing backend
    }

    public function search(SearchRequest $request): SearchResultCollection
    {
        // Mock implementatino of Indexing backend
    }
}
