<?php

namespace AutoAdmin\Bundle\Tests\Unit\Content\EventListener;

use AutoAdmin\Bundle\Content\Context\ContentContext;
use AutoAdmin\Bundle\Content\Event\ContentContextEventInterface;
use AutoAdmin\Bundle\Content\Event\ContentEvents;
use AutoAdmin\Bundle\Content\Event\ContentMatchEvent;
use AutoAdmin\Bundle\Content\EventListener\MainContentRequestSubscriber;
use AutoAdmin\Bundle\Content\Frontend\FrontendUrlChecker;
use AutoAdmin\Bundle\Seo\Model\Header;
use AutoAdmin\Bundle\Seo\Model\SeoPage;
use AutoAdmin\Bundle\Seo\Renderer\RendererManager;
use AutoAdmin\Bundle\Seo\Renderer\SeoRendererInterface;
use Doctrine\Common\Collections\ArrayCollection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\MockObject\Rule\InvokedCount;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use Setono\BotDetectionBundle\BotDetector\BotDetectorInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class MainContentRequestSubscriberTest extends TestCase
{
    /**
     * @dataProvider isSupportedRequestDataProvider
     */
    public function testIsSupportedRequest(KernelEvent $event, bool $isFrontUrl, bool $expected): void
    {
        $mainContentRequestSubscriber = $this->getMockBuilder(MainContentRequestSubscriber::class)
            ->disableOriginalConstructor()
            ->getMock();

        $frontEndUrlChecker = $this->getMockBuilder(FrontendUrlChecker::class)
            ->disableOriginalConstructor()
            ->getMock();

        $frontEndUrlChecker->method('isFrontUrl')->willReturn($isFrontUrl);

        $reflection = new ReflectionClass(MainContentRequestSubscriber::class);
        $reflection->getProperty('frontendUrlChecker')->setValue($mainContentRequestSubscriber, $frontEndUrlChecker);

        $method = $reflection->getMethod('isSupportedRequest');

        $result = $method->invokeArgs($mainContentRequestSubscriber, [$event]);

        $this->assertEquals($expected, $result);
    }

    /**
     * @dataProvider onKernelRequestDataProvider
     */
    public function testOnKernelRequest(bool $isSupportedRequest, InvokedCount $invocationRule): void
    {
        $mainContentRequestSubscriber = $this->getMockBuilder(MainContentRequestSubscriber::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['isSupportedRequest'])
            ->getMock();

        $mainContentRequestSubscriber->expects($this->once())
            ->method('isSupportedRequest')
            ->willReturn($isSupportedRequest);

        $requestEvent = $this->getMockBuilder(RequestEvent::class)
            ->disableOriginalConstructor()
            ->getMock();

        $contentContext = $this->getMockBuilder(ContentContext::class)
            ->disableOriginalConstructor()
            ->getMock();

        $eventDispatcher = $this->getMockBuilder(EventDispatcher::class)
            ->disableOriginalConstructor()
            ->getMock();

        $eventDispatcher->expects($invocationRule)
            ->method('dispatch')
            ->with(
                $this->isInstanceOf(ContentMatchEvent::class),
                $this->equalTo(ContentEvents::MATCH),
            );

        $reflection = new ReflectionClass(MainContentRequestSubscriber::class);
        $reflection->getProperty('contentContext')->setValue($mainContentRequestSubscriber, $contentContext);
        $reflection->getProperty('eventDispatcher')->setValue($mainContentRequestSubscriber, $eventDispatcher);

        $method = $reflection->getMethod('onKernelRequest');

        $method->invokeArgs($mainContentRequestSubscriber, [$requestEvent]);
    }

    /**
     * @dataProvider onKernelResponseDataProvider
     */
    public function testOnKernelResponse(
        bool $isSupportedRequest,
        InvokedCount $invocationRule,
        ?SeoPage $seoPage = null,
        bool $isBotRequest = false,
        array $renders = []
    ): void {
        $mainContentRequestSubscriber = $this->getMockBuilder(MainContentRequestSubscriber::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['isSupportedRequest', 'addRobotsTagHeader'])
            ->getMock();

        $mainContentRequestSubscriber->expects($this->once())
            ->method('isSupportedRequest')
            ->willReturn($isSupportedRequest);

        $mainContentRequestSubscriber->method('addRobotsTagHeader')
            ->willReturnCallback(function (Response $response, SeoPage $seoPage) {
                return $response;
            });

        $responseEvent = new ResponseEvent(
            $this->getMockBuilder(HttpKernelInterface::class)->getMock(),
            $this->getMockBuilder(Request::class)->getMock(),
            HttpKernelInterface::MAIN_REQUEST,
            new Response(),
        );

        $contentContext = $this->getMockBuilder(ContentContext::class)
            ->disableOriginalConstructor()
            ->getMock();

        $contentContext->method('getSeoPage')
            ->willReturn($seoPage);

        $eventDispatcher = $this->getMockBuilder(EventDispatcher::class)
            ->disableOriginalConstructor()
            ->getMock();

        $eventDispatcher->expects($invocationRule)
            ->method('dispatch')
            ->willReturnCallback(function (ContentContextEventInterface $key, string $value) use ($invocationRule) {
                match ($invocationRule->getInvocationCount()) {
                    1 => $this->assertEquals(ContentEvents::METADATA, $value),
                    2 => $this->assertEquals(ContentEvents::POST_METADATA, $value),
                };

                return $key;
            });

        $botDetector = $this->getMockBuilder(BotDetectorInterface::class)
            ->disableOriginalConstructor()
            ->getMock();

        $botDetector->method('isBotRequest')
            ->willReturn($isBotRequest);

        $rendererManager = $this->getMockBuilder(RendererManager::class)
            ->disableOriginalConstructor()
            ->getMock();

        $rendererManager->method('getRenderers')
            ->willReturn($renders);

        $reflection = new ReflectionClass(MainContentRequestSubscriber::class);
        $reflection->getProperty('contentContext')->setValue($mainContentRequestSubscriber, $contentContext);
        $reflection->getProperty('eventDispatcher')->setValue($mainContentRequestSubscriber, $eventDispatcher);
        $reflection->getProperty('botDetector')->setValue($mainContentRequestSubscriber, $botDetector);
        $reflection->getProperty('rendererManager')->setValue($mainContentRequestSubscriber, $rendererManager);

        $method = $reflection->getMethod('onKernelResponse');

        $result = $method->invokeArgs($mainContentRequestSubscriber, [$responseEvent]);

        $this->assertInstanceOf(Response::class, $result);
        if ($cookies = $result->headers->getCookies()) {
            $this->assertEquals('aa_is_bot', $cookies[0]->getName());
            $this->assertEquals($isBotRequest ? '1' : '', $cookies[0]->getValue());
            $this->assertFalse($cookies[0]->isSecure());
            $this->assertFalse($cookies[0]->isHttpOnly());
        }
    }

    /**
     * @dataProvider addRobotsTagHeaderDataProvider
     */
    public function testAddRobotsTagHeader(ArrayCollection $getResponseHeaders, array $expected): void
    {
        $mainContentRequestSubscriber = $this->getMockBuilder(MainContentRequestSubscriber::class)
            ->disableOriginalConstructor()
            ->getMock();

        $reflection = new ReflectionClass(MainContentRequestSubscriber::class);

        $method = $reflection->getMethod('addRobotsTagHeader');

        $seoPage = $this->getMockBuilder(SeoPage::class)
            ->disableOriginalConstructor()
            ->getMock();

        $seoPage->method('getResponseHeaders')
            ->willReturn($getResponseHeaders);

        $result = $method->invokeArgs($mainContentRequestSubscriber, [new Response(), $seoPage]);

        $this->assertInstanceOf(Response::class, $result);
        foreach ($expected as $key => $value) {
            $this->assertEquals($value, $result->headers->get($key));
        }
    }

    private function isSupportedRequestDataProvider(): array
    {
        return [
            'not main request and is frontend' => [
                $this->getEvent(false),
                true,
                false,
            ],
            'not main request and not frontend' => [
                $this->getEvent(false),
                false,
                false,
            ],
            'is main request and is frontend' => [
                $this->getEvent(true),
                true,
                true,
            ],
            'is main request and not frontend' => [
                $this->getEvent(true),
                false,
                false,
            ],
        ];
    }

    private function onKernelRequestDataProvider(): array
    {
        return [
            'not supported request' => [
                false,
                $this->never(),
            ],
            'supported request' => [
                true,
                $this->once(),
            ],
        ];
    }

    private function onKernelResponseDataProvider(): array
    {
        $renderer = $this->getMockBuilder(SeoRendererInterface::class)
            ->disableOriginalConstructor()
            ->getMock();

        $renderer->method('render')
            ->willReturnCallback(function (Response $response, SeoPage $seoPage) {
                return $response;
            });

        return [
            'not supported request' => [
                false,
                $this->never(),
            ],
            'supported request no SeoPage' => [
                true,
                $this->exactly(2),
            ],
            'supported request is bot' => [
                true,
                $this->exactly(2),
                null,
                true,
            ],
            'supported request SeoPage not renders' => [
                true,
                $this->exactly(2),
                $this->createMock(SeoPage::class),
            ],
            'supported request SeoPage with renders' => [
                true,
                $this->exactly(2),
                $this->createMock(SeoPage::class),
                false,
                [$renderer],
            ],
        ];
    }

    private function addRobotsTagHeaderDataProvider(): array
    {

        return [
            'no SeoPage' => [
                new ArrayCollection(),
                [],
            ],
            'SeoPage' => [
                new ArrayCollection([
                    new Header('X-Robots-Tag', 'noindex, nofollow'),
                ]),
                [
                    'X-Robots-Tag' => 'noindex, nofollow',
                ],
            ],
            'SeoPage multiple' => [
                new ArrayCollection([
                    new Header('X-Robots-Tag', 'noindex, nofollow'),
                    new Header('X-Robots-Gate', 'test'),
                ]),
                [
                    'X-Robots-Tag' => 'noindex, nofollow',
                    'X-Robots-Gate' => 'test',
                ],
            ],
            'SeoPage multiple exist' => [
                new ArrayCollection([
                    new Header('X-Robots-Tag', 'noindex, nofollow'),
                    new Header('X-Robots-Tag', 'test'),
                ]),
                [
                    'X-Robots-Tag' => 'noindex, nofollow',
                ],
            ],
        ];
    }

    private function getEvent(bool $isMainRequest,): KernelEvent|MockObject
    {
        $event = $this->getMockBuilder(KernelEvent::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['isMainRequest', 'getRequest'])
            ->getMock();

        $event->method('isMainRequest')
            ->willReturn($isMainRequest);

        return $event;
    }
}
