<?php

namespace AutoAdmin\Bundle\Seo\Extractor;

use AutoAdmin\Bundle\Content\Configuration\ConfigManager;
use AutoAdmin\Bundle\Content\Entity\Page;
use AutoAdmin\Bundle\Content\Model\TranslatableInterface;
use AutoAdmin\Bundle\Content\Model\TranslationInterface;
use AutoAdmin\Bundle\Content\Routing\ContentRouter;
use AutoAdmin\Bundle\Content\Site\SiteContextHelper;
use AutoAdmin\Bundle\Seo\Entity\SeoMetadata;
use AutoAdmin\Bundle\Seo\Model\Alternate;
use AutoAdmin\Bundle\Seo\Model\Header;
use AutoAdmin\Bundle\Seo\Model\Meta;
use AutoAdmin\Bundle\Seo\Model\SeoContentInterface;
use AutoAdmin\Bundle\Seo\Model\SeoContentTranslationInterface;
use AutoAdmin\Bundle\Seo\Model\SeoPage;
use ReflectionClass;
use Symfony\Component\HttpFoundation\Request;

class SeoContentExtractor extends AbstractExtractor
{
    public function __construct(
        protected ConfigManager $config,
        protected ContentRouter $contentRouter,
        protected SiteContextHelper $contextHelper,
    ) {
    }

    public function supports(object $subject): bool
    {
        return $subject instanceof SeoContentInterface;
    }

    public function extract(Request $request, object $subject, SeoPage $seoPage): SeoPage
    {
        [$translatable, $translation] = $this->getTranslatableTranslationObjects($subject);

        if (
            $translation instanceof SeoContentTranslationInterface
            && ($seoMetadata = $translation->getSeoMetadata()) !== null
        ) {
            $this->handleTitle($seoMetadata, $seoPage);

            $this->handleDescription($seoMetadata, $seoPage);

            $this->handleMetas($seoMetadata, $seoPage);

            $this->handleCanonical($seoMetadata, $seoPage, $request);

            $this->handleRobotsTag($seoMetadata, $seoPage);
        }

        if ($translatable instanceof TranslatableInterface) {
            $this->handleAlternates($translatable, $seoPage, $request);
        }

        return $seoPage;
    }

    protected function handleTitle(SeoMetadata $seoMetadata, SeoPage $seoPage): void
    {
        if (!$seoMetadata->getTitle()) {
            return;
        }

        $seoPage->setTitle($this->sanitize($seoMetadata->getTitle()));
    }

    protected function handleDescription(SeoMetadata $seoMetadata, SeoPage $seoPage): void
    {
        if (!$seoMetadata->getDescription()) {
            return;
        }

        $seoPage->addMeta(
            new Meta(
                'description',
                $this->sanitize($seoMetadata->getDescription())
            )
        );
    }

    protected function handleMetas(SeoMetadata $seoMetadata, SeoPage $seoPage): void
    {
        if (empty($metas = $seoMetadata->getMetas())) {
            return;
        }

        foreach ($metas as $meta => $value) {
            $seoPage->addMeta(new Meta($meta, $value));
        }
    }

    protected function handleCanonical(SeoMetadata $seoMetadata, SeoPage $seoPage, Request $request): void
    {
        if (!empty($seoMetadata->getCanonicalOverride())) {
            $seoPage->setCanonical($seoMetadata->getCanonicalOverride());
        } else {
            $seoPage->setCanonical(strtok($request->getUri(), '?'));
        }
    }

    protected function handleRobotsTag(SeoMetadata $seoMetadata, SeoPage $seoPage): void
    {
        $xRobotsTag = [];
        if ($seoMetadata->isNoIndex()) {
            $xRobotsTag[] = 'noindex';
        }

        if ($seoMetadata->isNoFollow()) {
            $xRobotsTag[] = 'nofollow';
        }

        if ($xRobotsTag) {
            $seoPage->addResponseHeader(
                new Header(
                    'X-Robots-Tag',
                    implode(', ', $xRobotsTag)
                )
            );
        }
    }

    protected function handleAlternates(TranslatableInterface $translatable, SeoPage $seoPage, Request $request): void
    {
        $alternates = [];
        $route = $request->get('_route');
        $action = $this->config->getRouteAction($route);

        // null action usually mean we are out of content routes, for example in contact controller
        // use the main content if page is provided and override route and action
        if ($action === null && (new ReflectionClass($translatable))->isSubclassOf(Page::class)) {
            $route = 'page';
            $action = 'view';
        }

        /** @var TranslationInterface $translation */
        foreach ($translatable->getTranslations() as $translation) {
            $locale = $translation->getLocale();
            // check if route is localized (route name ending with locale part: .fr, .en)
            // if that's the case, get right locale
            if (
                preg_match("/(.+?)(\.([a-z]{2}))/", $route, $matches)
                && !empty($localeCode = $matches[3])
            ) {
                $locale = $this->contextHelper->getSiteLocaleForCode($localeCode);
            }

            if ($locale === null) {
                throw new \RuntimeException('Locale is required for translatable content');
            }

            $site = $translatable->getSite();
            if ($site === null) {
                throw new \RuntimeException('Site is required for translatable content');
            }

            $domain = $this->contextHelper->getDomainByLocale($site, $locale);
            if ($domain === null) {
                throw new \RuntimeException('Domain is required for translatable content');
            }

            $url = $this->contentRouter->generate(
                $translatable,
                $action,
                ['_locale' => $locale->getCode()],
                ['host' => $domain->getDomain()]
            );

            $alternates[] = new Alternate($locale, $url);
        }

        $seoPage->setAlternates($alternates);
    }
}
