<?php declare(strict_types=1);

namespace Newland\Toubiz\Events\Neos\Tests\Integration;

use Doctrine\Common\Collections\ArrayCollection;
use Neos\Flow\Utility\Now;
use Newland\Toubiz\Sync\Neos\Domain\Model\Event;
use Newland\Toubiz\Sync\Neos\Domain\Model\EventDate;
use Newland\Toubiz\Sync\Neos\Domain\Model\RecordConfiguration;
use Newland\Toubiz\Sync\Neos\Domain\Repository\EventRepository;
use Ramsey\Uuid\Uuid;

class DetailPageTest extends IntegrationTestCase
{

    /** @var Event */
    protected $event;

    /** @var EventDate[] */
    protected $dates;


    public function setUp(): void
    {
        parent::setUp();

        $this->objectManager->setInstance(Now::class, new Now('2019-01-01T06:00'));

        $this->event = $this->eventFactory->create(
            [
                'originalId' => 'c9354e59-97da-39e6-8093-31eeeed96c3a',
                'title' => 'foo bar test',
                'client' => 'san flamingo',
            ]
        );
        $this->persist($this->event);

        $this->dates = [
            $this->eventDateFactory->create(
                [ 'beginsAt' => '2019-01-01T14:00', 'endsAt' => '2019-01-01T17:00', 'event' => $this->event ]
            ),
            $this->eventDateFactory->create(
                [ 'beginsAt' => '2019-01-02T12:00', 'endsAt' => '2019-01-02T15:00', 'event' => $this->event ]
            ),
            $this->eventDateFactory->create(
                [ 'beginsAt' => '2019-01-03T06:00', 'endsAt' => '2019-01-03T18:00', 'event' => $this->event ]
            ),
        ];

        $this->event->setEventDates(new ArrayCollection($this->dates));
        $this->persist($this->event);
    }

    public function tearDown(): void
    {
        $this->objectManager->forgetInstance(Now::class);
        parent::tearDown();
    }

    public function testRendersDetailView(): void
    {
        $response = $this->browser->request(
            $this->eventUrlService->generateUrlByCurrentNode($this->event, $this->documentNode)
        );
        $this->assertResponseOk($response);
    }

    public function testRendersDetailViewForDates(): void
    {
        $this->assertResponseOk(
            $this->browser->request(
                $this->eventUrlService->generateUrlByCurrentNode(
                    $this->event,
                    $this->documentNode,
                    $this->dates[0]
                )
            )
        );
        $this->assertResponseOk(
            $this->browser->request(
                $this->eventUrlService->generateUrlByCurrentNode(
                    $this->event,
                    $this->documentNode,
                    $this->dates[1]
                )
            )
        );
        $this->assertResponseOk(
            $this->browser->request(
                $this->eventUrlService->generateUrlByCurrentNode(
                    $this->event,
                    $this->documentNode,
                    $this->dates[2]
                )
            )
        );
    }

    // See TBEVENTS-132
    public function testRendersTimesOfRecurringEventsCorrectly(): void
    {
        $response = $this->browser->request(
            $this->eventUrlService->generateUrlByCurrentNode($this->event, $this->documentNode)
        );
        $this->assertResponseOk($response);

        $bodyWithoutTags = preg_replace(
            '/\s+/',
            ' ',
            strip_tags((string) $response->getBody())
        );
        self::assertContains('01.01.2019 | 14:00 – 17:00', $bodyWithoutTags);
        self::assertContains('02.01.2019 | 12:00 – 15:00', $bodyWithoutTags);
        self::assertContains('03.01.2019 | 06:00 – 18:00', $bodyWithoutTags);
    }

    public function testRendersNoTimeIfThereIsNoSpecificTime(): void
    {
        $this->dates[0]->setBeginsAt(new \DateTime('2055-01-01T00:00'));
        $this->dates[0]->setBeginsAtSpecificTime(false);
        $this->dates[0]->setEndsAt(new \DateTime('2055-01-01T23:59'));
        $this->dates[0]->setEndsAtSpecificTime(false);
        $this->persist($this->dates[0]);

        $response = $this->browser->request(
            $this->eventUrlService->generateUrlByCurrentNode($this->event, $this->documentNode)
        );
        $this->assertResponseOk($response);

        $body = preg_replace('/\s+/', ' ', (string) $response->getBody());
        self::assertStringNotContainsString('00:00', $body);
        self::assertStringNotContainsString('23:59', $body);
    }

    public function testRendersNoEndTimeIfItIsNotSpecific(): void
    {
        $this->dates[0]->setBeginsAt(new \DateTime('2055-01-01T14:00'));
        $this->dates[0]->setBeginsAtSpecificTime(true);
        $this->dates[0]->setEndsAt(new \DateTime('2055-01-01T23:59'));
        $this->dates[0]->setEndsAtSpecificTime(false);
        $this->persist($this->dates[0]);

        $response = $this->browser->request(
            $this->eventUrlService->generateUrlByCurrentNode($this->event, $this->documentNode)
        );
        $this->assertResponseOk($response);

        $body = preg_replace('/\s+/', ' ', (string) $response->getBody());
        self::assertStringContainsString('14:00', $body);
        self::assertStringNotContainsString('23:59', $body);
    }

    public function testDoesNotRenderAnyTimeIfStartTimeIsNotSpecific(): void
    {
        $this->dates[0]->setBeginsAt(new \DateTime('2055-01-01T00:00'));
        $this->dates[0]->setBeginsAtSpecificTime(false);
        $this->dates[0]->setEndsAt(new \DateTime('2055-01-01T14:42'));
        $this->dates[0]->setEndsAtSpecificTime(true);
        $this->persist($this->dates[0]);

        $response = $this->browser->request(
            $this->eventUrlService->generateUrlByCurrentNode($this->event, $this->documentNode)
        );
        $this->assertResponseOk($response);

        $body = preg_replace('/\s+/', ' ', (string) $response->getBody());
        self::assertStringNotContainsString('00:00', $body);
        self::assertStringNotContainsString('14:42', $body);
    }

    public function testDisplaysSourceIfNotDisabled(): void
    {
        $sourceName = sprintf('__SOURCE__NAME__%s__', md5(random_bytes(32)));
        $this->event->setSourceName($sourceName);

        $this->withMockedConfiguration(
            [ 'Newland.Toubiz.Events.Neos.displayOptions.show.hide' => [] ],
            function () use ($sourceName) {
                $response = $this->browser->request(
                    $this->eventUrlService->generateUrlByCurrentNode($this->event, $this->documentNode)
                );
                $this->assertResponseOk($response);
                $this->assertResponseContains($sourceName, $response);
            }
        );
    }

    public function testDoesNotDisplaySourceIfDisabled(): void
    {
        $sourceName = sprintf('__SOURCE__NAME__%s__', md5(random_bytes(32)));
        $this->event->setSourceName($sourceName);

        $this->withMockedConfiguration(
            [ 'Newland.Toubiz.Events.Neos.displayOptions.show.hide' => [ 'source' ] ],
            function () use ($sourceName) {
                $response = $this->browser->request(
                    $this->eventUrlService->generateUrlByCurrentNode($this->event, $this->documentNode)
                );
                $this->assertResponseOk($response);
                $this->assertResponseNotContains($sourceName, $response);
            }
        );
    }

    public function testUrlsWithoutUrlIdentifiersRedirectCorrectly(): void
    {
        $this->event->setUrlIdentifier(md5(random_bytes(32)));
        $this->event->setLanguage('de');
        $this->persist($this->event);

        $url = $this->eventUrlService->generateUrlByCurrentNode($this->event, $this->node);
        $urlWithoutHash = str_replace('-' . $this->event->getUrlIdentifier(), '', $url);

        $this->browser->setFollowRedirects(false);
        $response = $this->browser->request($urlWithoutHash);

        self::assertEquals(301, $response->getStatusCode());
        self::assertEquals($url, $response->getHeader('Location'));
    }

    public function testUrlsWithIncorrectPathRedirectToCorrectUrlIdentifier(): void
    {
        $this->event->setUrlIdentifier(md5(random_bytes(32)));
        $this->event->setLanguage('de');
        $this->persist($this->event);

        $url = $this->eventUrlService->generateUrlByCurrentNode($this->event, $this->node);
        $urlParts = explode('/', $url);
        $urlParts[count($urlParts) - 1] = 'foo-bar-' . $this->event->getUrlIdentifier();
        $urlWithIncorrectNamePart = implode('/', $urlParts);

        $this->browser->setFollowRedirects(false);
        $response = $this->browser->request($urlWithIncorrectNamePart);

        self::assertEquals(301, $response->getStatusCode());
        self::assertEquals($url, $response->getHeader('Location'));
    }

    public function testDoesNotShowHiddenEventOnDetailPage(): void
    {
        self::markTestSkipped();

        $config = new RecordConfiguration();
        $config->setConfiguration([ 'hidden' => true ]);
        $this->event->applyRecordConfiguration($config);
        $this->objectManager->get(EventRepository::class)->update($this->event);
        $this->persistenceManager->persistAll();
        $this->persistenceManager->clearState();

        $response = $this->browser->request($this->eventUrl());
        self::assertEquals(404, $response->getStatusCode());
    }

    public function testContainsLanguageMenuLinkToAlternativeLanguage(): void
    {
        $this->createNodeLanguageVariants($this->siteNode, [ 'en', 'fr' ]);

        $id = (string) Uuid::uuid4();
        [ $german ] = $this->createEventWithDate(
            [
                'languageGrouping' => $id,
                'name' => 'the-german-one',
                'language' => 'de',
                'urlIdentifier' => md5(random_bytes(32)),
            ]
        );
        [ $english ] = $this->createEventWithDate(
            [
                'languageGrouping' => $id,
                'name' => 'the-english-one',
                'language' => 'en',
                'urlIdentifier' => md5(random_bytes(32)),
            ]
        );
        [ $french ] = $this->createEventWithDate(
            [
                'languageGrouping' => $id,
                'name' => 'the-french-one',
                'language' => 'fr',
                'urlIdentifier' => md5(random_bytes(32)),
            ]
        );

        $response = $this->browser->request($this->eventUrl($german));
        $this->assertResponseOk($response);

        $this->assertResponseContains(
            sprintf('<a href=".*?%s"', $german->getUrlIdentifier()),
            $response,
            'Should contain language navigation link to german version'
        );
        $this->assertResponseContains(
            sprintf('<a href=".*?%s"', $english->getUrlIdentifier()),
            $response,
            'Should contain language navigation link to english version'
        );
        $this->assertResponseContains(
            sprintf('<a href=".*?%s"', $french->getUrlIdentifier()),
            $response,
            'Should contain language navigation link to french version'
        );
    }

    public function testContainsHrefLangLinkToAlternativeLanguage(): void
    {
        $this->createNodeLanguageVariants($this->siteNode, [ 'en', 'fr' ]);

        $id = (string) Uuid::uuid4();
        [ $german ] = $this->createEventWithDate(
            [
                'languageGrouping' => $id,
                'name' => 'the-german-one',
                'language' => 'de',
                'urlIdentifier' => md5(random_bytes(32)),
            ]
        );
        [ $english ] = $this->createEventWithDate(
            [
                'languageGrouping' => $id,
                'name' => 'the-english-one',
                'language' => 'en',
                'urlIdentifier' => md5(random_bytes(32)),
            ]
        );
        [ $french ] = $this->createEventWithDate(
            [
                'languageGrouping' => $id,
                'name' => 'the-french-one',
                'language' => 'fr',
                'urlIdentifier' => md5(random_bytes(32)),
            ]
        );

        $response = $this->browser->request($this->eventUrl($german));
        $this->assertResponseOk($response);

        $this->assertResponseContains(
            sprintf('<link rel="alternate" hreflang="%s" href=".*?%s"', 'de', $german->getUrlIdentifier()),
            $response,
            'Should contain hreflang link to german version'
        );
        $this->assertResponseContains(
            sprintf('<link rel="alternate" hreflang="%s" href=".*?%s"', 'en', $english->getUrlIdentifier()),
            $response,
            'Should contain hreflang link to english version'
        );
        $this->assertResponseContains(
            sprintf('<link rel="alternate" hreflang="%s" href=".*?%s"', 'fr', $french->getUrlIdentifier()),
            $response,
            'Should contain hreflang link to french version'
        );
    }

    private function eventUrl(Event $event = null): string
    {
        return $this->eventUrlService->generateUrlByCurrentNode($event ?? $this->event, $this->documentNode);
    }


    public function testRendersSlots(): void
    {
        $response = $this->browser->request($this->eventUrl());
        $this->assertResponseOk($response);

        foreach ($this->provideSlotsThatShouldExist() as [$slot]) {
            $this->assertResponseContains(sprintf('Slot(%s)', $slot), $response, null, false);
        }
    }

    public function provideSlotsThatShouldExist(): array
    {
        return [
            [ 'aboveAll' ],
            [ 'belowAll' ],
            [ 'aboveTitle' ],
            [ 'belowTitle' ],
            [ 'aboveDetail' ],
            [ 'belowDetail' ],
            [ 'aboveDetailMainContent' ],
            [ 'belowDetailMainContent' ],
            [ 'aboveDetailAside' ],
            [ 'belowDetailAside' ],
        ];
    }
}
