<?php

namespace AutoAdmin\Bundle\Content\Site;

use AutoAdmin\Bundle\Content\Context\SiteContext;
use AutoAdmin\Bundle\Content\Entity\Domain;
use AutoAdmin\Bundle\Content\Entity\Locale;
use AutoAdmin\Bundle\Content\Repository\DomainRepository;
use AutoAdmin\Bundle\Content\Translation\EntityTranslator;
use Doctrine\Persistence\ManagerRegistry;
use RuntimeException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class SiteMatcher
{
    public function __construct(
        protected ManagerRegistry $doctrine,
        protected EntityTranslator $entityTranslator,
        protected $appEnv,
    )
    {
    }

    public function matchSite(Request $request, SiteContextHelper $contextHelper): void
    {
        // ignore /media/*** urls to avoid trying domain matching on static files
        if (
            str_starts_with($request->getRequestUri(), "/media/")
            && !str_starts_with($request->getRequestUri(), "/media/cache/resolve/")
        ) {
            throw new NotFoundHttpException();
        }

        /** @var DomainRepository $domainRepo */
        $domainRepo = $this->doctrine->getRepository(Domain::class);
        $localeRepo = $this->doctrine->getRepository(Locale::class);

        $allowedLocales = $localeRepo->findAll();
        $allowedLocaleCodes = array_map(fn(Locale $locale) => $locale->getCode(), $allowedLocales);

        $parsedUrl = parse_url($request->getRequestUri());
        if (isset($parsedUrl['path']) && stripos($parsedUrl['path'], '//') !== false) {
            throw new NotFoundHttpException('Double slash in url');
        }

        if ($request->attributes->has('_locale')) {
            $localeValue = $request->attributes->get('_locale');
            if (!in_array($localeValue, $allowedLocaleCodes)) {
                throw new NotFoundHttpException('Bad locale format');
            }
        }

        $matchedLocale = null;

        // get locale from route
        if (null !== $request->get('_admin_locale')) {
            $matchedLocale = $request->get('_admin_locale');
        }

        if (null !== $request->get('_locale')) {
            $matchedLocale = $request->get('_locale');
        }

        // get locale using host
        if (is_null($matchedLocale)) {
            $routeParams = $request->get('_route_params');
            if (!empty($routeParams['_locale'])) {
                $matchedLocale = $routeParams['_locale'];
            }
        }

        $host = $request->getHost();
        if (is_null($matchedLocale)) {
            $matchedLocale = $this->findDefaultLocale($host);
        }

        // match domain with locale
        $domain = $domainRepo->findMatchingHost($host, $matchedLocale);
        if (empty($domain)) {
            throw new RuntimeException('Could not find matching domain.');
        }

        $site = $domain->getSite();

        $context = new SiteContext();
        $context->setSite($site);

        /** @var Locale|null $locale */
        $locale = $localeRepo->findOneBy(['code' => $matchedLocale]);
        $context->setLocale($locale);

        if ($context->getSite() === null || $context->getLocale() === null) {
            throw new RuntimeException('Could not match site entity with available information.');
        }

        // init our context on main helper
        $contextHelper->loadContext($context);

        // finally, set locale on related services
        $this->setupTranslatorLocale($context->getLocale());
    }

    /**
     * Mostly used for untranslated sites, or for "/" path of suffix-translated domains
     */
    public function findDefaultLocale(string $host): ?string
    {
        $domainRepo = $this->doctrine->getRepository(Domain::class);
        /** @var Domain[] $domains */
        $domains = $domainRepo->findBy(['domain' => $host,]);

        foreach ($domains as $domain) {
            $locales = $domain->getLocales();
            foreach ($locales as $locale) {
                if ($locale) {
                    return $locale->getCode();
                }
            }
        }

        throw new RuntimeException('Could not match any default locale or no domain matching "' . $host . '".');
    }

    protected function setupTranslatorLocale(Locale $locale): void
    {
        $this->entityTranslator->setLocale($locale);
    }
}
