<?php

namespace AutoAdmin\Bundle\Tests\Unit\Seo\Extractor;

use AutoAdmin\Bundle\Content\Context\SiteContext;
use AutoAdmin\Bundle\Content\Entity\Domain;
use AutoAdmin\Bundle\Content\Entity\Locale;
use AutoAdmin\Bundle\Content\Entity\Page;
use AutoAdmin\Bundle\Content\Entity\PageTranslation;
use AutoAdmin\Bundle\Content\Entity\Site;
use AutoAdmin\Bundle\Content\Model\SiteConfig;
use AutoAdmin\Bundle\Content\Site\SiteContextHelper;
use AutoAdmin\Bundle\Content\Translation\LocaleSwitchHelper;
use AutoAdmin\Bundle\Content\Translation\Model\LocaleSwitch;
use AutoAdmin\Bundle\Seo\Entity\SeoMetadata;
use AutoAdmin\Bundle\Seo\Extractor\SeoContentExtractor;
use AutoAdmin\Bundle\Seo\Model\Alternate;
use AutoAdmin\Bundle\Seo\Model\Header;
use AutoAdmin\Bundle\Seo\Model\Meta;
use AutoAdmin\Bundle\Seo\Model\SeoContentTranslationInterface;
use AutoAdmin\Bundle\Seo\Model\SeoPage;
use DateTime;
use stdClass;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;

class SeoContentExtractorTest extends KernelTestCase
{
    protected function setUp(): void
    {
        self::bootKernel();

        $domain = new Domain();
        $domain->setDomain('test.fr');
        $domain->addLocale($this->getLocale());

        $site = new Site();
        $site->setEnabled(true);
        $site->setCreatedAt(new DateTime('2022-01-01 00:00:00'));
        $site->setUpdatedAt(new DateTime('2022-01-01 00:00:00'));
        $site->setTitle('Test');
        $site->setSlug('test');
        $site->addDomain($domain);

        $siteContext = new SiteContext();
        $siteContext->setSite($site);
        $siteContext->setLocale($this->getLocale());

        self::getContainer()->get(SiteContextHelper::class)->loadContext($siteContext);
    }

    private function getLocale(): Locale
    {
        $locale = new Locale();
        $locale->setCode('fr');

        return $locale;
    }

    /**
     * @dataProvider supportsDataProvider
     */
    public function testSupports(
        bool $isTranslatable,
        bool $isTranslation,
        bool $isUntranslatedContent,
        bool $expected
    ): void {
        $extractor = $this->getMockBuilder(SeoContentExtractor::class)
            ->disableOriginalConstructor()
            ->onlyMethods(['isTranslatable', 'isTranslation', 'isUntranslatedContent'])
            ->getMock();

        $extractor->method('isTranslatable')
            ->willReturn($isTranslatable);

        $extractor->method('isTranslation')
            ->willReturn($isTranslation);

        $extractor->method('isUntranslatedContent')
            ->willReturn($isUntranslatedContent);

        $this->assertEquals($expected, $extractor->supports(new stdClass()));
    }

    private function supportsDataProvider(): array
    {
        return [
            'is translatable' => [
                true,
                false,
                false,
                true,
            ],
            'is translation' => [
                false,
                true,
                false,
                true,
            ],
            'is untranslated content' => [
                false,
                false,
                true,
                true,
            ],
            'is not translatable content' => [
                false,
                true,
                true,
                true,
            ],
            'is not translation content' => [
                true,
                false,
                true,
                true,
            ],
            'is not untranslated content' => [
                true,
                true,
                false,
                true,
            ],
            'is all content' => [
                true,
                true,
                true,
                true,
            ],
            'is not content' => [
                false,
                false,
                false,
                false,
            ],
        ];
    }

    /**
     * @dataProvider extractDataProvider
     */
    public function testExtract(
        Request $request,
        object $subject,
        SeoPage $seoPage,
        ?string $exception,
        ?SeoPage $expected,
    ): void {
        if ($exception !== null) {
            $this->expectExceptionMessage($exception);
        }

        $seoContentExtractor = $this->getContainer()->get(SeoContentExtractor::class);

        $localeSwitchHelper = $this->getMockBuilder(LocaleSwitchHelper::class)
            ->disableOriginalConstructor()
            ->getMock();

        $localeSwitchHelper->method('buildCurrentSwitch')
            ->willReturn(
                new LocaleSwitch(
                    siteConfig: (function () {
                        $mock = $this->createMock(SiteConfig::class);
                        $mock->method('getLocale')
                            ->willReturn($this->getLocale());

                        return $mock;
                    })(),
                    url: 'test',
                )
            );
        $localeSwitchHelper->method('buildAlternateSwitches')
            ->willReturn([]);

        $reflection = new \ReflectionClass(SeoContentExtractor::class);
        $reflection->getProperty('localeSwitchHelper')
            ->setValue($seoContentExtractor, $localeSwitchHelper);

        $seoContentExtractor->extract($request, $subject, $seoPage);

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

    private function extractDataProvider(): array
    {
        return [
            'bad main content' => [
                'request' => $this->createMock(Request::class),
                'subject' => new stdClass(),
                'seoPage' => new SeoPage(),
                'exception' => 'Main content should implement some kind of SEO',
                'expected' => null,
            ],
            'seo content no new data' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn($this->createMock(SeoMetadata::class));

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => new SeoPage(),
            ],
            'seo content new title' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn(
                            (function () {
                                $mock = $this->createMock(SeoMetadata::class);
                                $mock->method('getTitle')
                                    ->willReturn('test');

                                return $mock;
                            })()
                        );

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->setTitle('test');

                    return $seoPage;
                })(),
            ],
            'seo content new title sanitize' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn(
                            (function () {
                                $mock = $this->createMock(SeoMetadata::class);
                                $mock->method('getTitle')
                                    ->willReturn('<h1>test</h1>');

                                return $mock;
                            })()
                        );

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->setTitle('test');

                    return $seoPage;
                })(),
            ],
            'seo content new description' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn(
                            (function () {
                                $mock = $this->createMock(SeoMetadata::class);
                                $mock->method('getDescription')
                                    ->willReturn('test');

                                return $mock;
                            })()
                        );

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->addMeta(new Meta('description', 'test'));

                    return $seoPage;
                })(),
            ],
            'seo content new description sanitize' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn(
                            (function () {
                                $mock = $this->createMock(SeoMetadata::class);
                                $mock->method('getDescription')
                                    ->willReturn('<h1>test</h1>');

                                return $mock;
                            })()
                        );

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->addMeta(new Meta('description', 'test'));

                    return $seoPage;
                })(),
            ],
            'seo content new metas' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn(
                            (function () {
                                $mock = $this->createMock(SeoMetadata::class);
                                $mock->method('getMetas')
                                    ->willReturn([
                                        'test-name' => 'test-value',
                                        'test2-name' => 'test2-value',
                                    ]);

                                return $mock;
                            })()
                        );

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->addMeta(new Meta('test-name', 'test-value'));
                    $seoPage->addMeta(new Meta('test2-name', 'test2-value'));

                    return $seoPage;
                })(),
            ],
            'seo content new canonical by override' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn(
                            (function () {
                                $mock = $this->createMock(SeoMetadata::class);
                                $mock->method('getCanonicalOverride')
                                    ->willReturn('test');

                                return $mock;
                            })()
                        );

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->setCanonical('test');

                    return $seoPage;
                })(),
            ],
            'seo content new canonical by request' => [
                'request' => (function () {
                    $mock = $this->createMock(Request::class);
                    $mock->method('getUri')
                        ->willReturn('test');

                    return $mock;
                })(),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn($this->createMock(SeoMetadata::class));

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->setCanonical('test');

                    return $seoPage;
                })(),
            ],
            'seo content new canonical by request with params' => [
                'request' => (function () {
                    $mock = $this->createMock(Request::class);
                    $mock->method('getUri')
                        ->willReturn('test?test=1');

                    return $mock;
                })(),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn($this->createMock(SeoMetadata::class));

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->setCanonical('test');

                    return $seoPage;
                })(),
            ],
            'seo content new robots tag noindex' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn(
                            (function () {
                                $mock = $this->createMock(SeoMetadata::class);
                                $mock->method('isNoIndex')
                                    ->willReturn(true);

                                return $mock;
                            })()
                        );

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->addResponseHeader(new Header('X-Robots-Tag', 'noindex'));

                    return $seoPage;
                })(),
            ],
            'seo content new robots tag nofollow' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn(
                            (function () {
                                $mock = $this->createMock(SeoMetadata::class);
                                $mock->method('isNoFollow')
                                    ->willReturn(true);

                                return $mock;
                            })()
                        );

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->addResponseHeader(new Header('X-Robots-Tag', 'nofollow'));

                    return $seoPage;
                })(),
            ],
            'seo content new robots tag noindex and nofollow' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(SeoContentTranslationInterface::class);
                    $mock->method('getSeoMetadata')
                        ->willReturn(
                            (function () {
                                $mock = $this->createMock(SeoMetadata::class);
                                $mock->method('isNoIndex')
                                    ->willReturn(true);
                                $mock->method('isNoFollow')
                                    ->willReturn(true);

                                return $mock;
                            })()
                        );

                    return $mock;
                })(),
                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->addResponseHeader(new Header('X-Robots-Tag', 'noindex, nofollow'));

                    return $seoPage;
                })(),
            ],
            'alternate with new data' => [
                'request' => $this->createMock(Request::class),
                'subject' => (function () {
                    $mock = $this->createMock(Page::class);
                    $mock->method('getCurrentTranslation')
                        ->willReturn($this->createMock(PageTranslation::class));

                    return $mock;
                })(),

                'seoPage' => new SeoPage(),
                'exception' => null,
                'expected' => (function () {
                    $seoPage = new SeoPage();
                    $seoPage->setAlternates([
                        new Alternate($this->getLocale(), 'test'),
                    ]);

                    return $seoPage;
                })(),
            ],
        ];
    }
}
