<?php

namespace AutoAdmin\Bundle\Seo\Redirect;

use AutoAdmin\Bundle\Content\Entity\Domain;
use AutoAdmin\Bundle\Content\Frontend\FrontendUrlChecker;
use AutoAdmin\Bundle\Seo\Entity\Redirect;
use AutoAdmin\Bundle\Seo\Repository\RedirectRepository;
use DateTime;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use RuntimeException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class RedirectService
{
    public function __construct(
        protected ManagerRegistry $doctrine,
        protected FrontendUrlChecker $frontendUrlChecker,
    ) {
    }

    public function getRedirectResponse(Request $request, bool $rootRedirect = false): ?Response
    {
        if ($this->frontendUrlChecker->isFrontUrl($request)) {
            return null;
        }

        $host = $request->getHost();
        $uri = $request->getRequestUri();

        $found = $this->getRedirect($host, $uri, $rootRedirect);
        if (!$found) {
            return null;
        }

        try {
            $destination = $this->replacePlaceholders($found, $uri);
        } catch (Exception) {
            return null;
        }

        return new RedirectResponse(
            $destination,
            $found->getType() ?: Response::HTTP_FOUND,
            ['X-Custom-Redirection' => '1']
        );
    }

    public function getRedirect(string $host, string $uri, bool $rootRedirect = false): ?Redirect
    {
        /** @var ?Domain $domain */
        $domain = $this->doctrine->getRepository(Domain::class)->findOneBy(['domain' => $host]);

        if ($rootRedirect === false) {
            if (empty($domain)) {
                throw new RuntimeException('Could not find matching domain.');
            }

            $site = $domain->getSite();
            if (empty($site)) {
                throw new RuntimeException('Could not find matching site.');
            }
        } else {
            $site = null;
        }

        /** @var RedirectRepository $repo */
        $repo = $this->doctrine->getRepository(Redirect::class);

        // TODO: optimize speed by caching redirects data to avoid db query
        $found = $repo->findOneByExactOrigin($uri, $rootRedirect, $site);
        if (!$found) {
            $found = $repo->findOneByOriginLike($uri, $rootRedirect, $site);
        }

        if (!$found) {
            return null;
        }

        $found->setCounter($found->getCounter() + 1);
        $found->setLastHit(new DateTime());
        $entityManager = $this->doctrine->getManager();
        $entityManager->persist($found);
        $entityManager->flush();

        return $found;
    }

    private function replacePlaceholders(Redirect $found, string $uri): string
    {
        $destination = $found->getDestination();
        $originRegex = $found->getOrigin();

        // Format regex
        $originRegex = preg_replace('#(\*\*)#', '(.+)', $originRegex);
        $originRegex = str_replace('/', '\/', $originRegex);
        $originRegex = str_replace('?', '\?', $originRegex);
        $originRegex = preg_replace('#(\*[^*]+\*)#', '([^\/]+)', $originRegex);
        $originRegex = preg_replace('#(\*)#', '([^\/]+)', $originRegex);

        // Get matches with regex
        if (
            !preg_match("#^$originRegex$#i", $uri, $matches)
            && !preg_match("#^$originRegex$#i", urldecode($uri), $matches)
        ) {
            throw new Exception('No match found');
        }

        // Assign matches to destination
        foreach ($matches as $key => $match) {
            $destination = str_replace("*$key*", $match, $destination);
            if (stripos($destination, '**') !== false) {
                $destination = str_replace("**", $matches[1], $destination);
            }
        }

        if (preg_match('/(\*[0-9]+\*)/', $destination)) {
            throw new Exception('No match found: Missing placeholder in origin');
        }

        return $destination;
    }
}
