<?php

namespace AutoAdmin\Bundle\Content\EventListener;

use AutoAdmin\Bundle\Content\Configuration\ConfigManager;
use AutoAdmin\Bundle\Content\Entity\AbstractContent;
use AutoAdmin\Bundle\Content\Event\ContentMatchEvent;
use AutoAdmin\Bundle\Content\Repository\DefaultTranslatableContentRepository;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use Psr\Log\LoggerInterface;
use ReflectionClass;
use Symfony\Component\HttpFoundation\Request;

class ContentMatcherListener
{
    protected ConfigManager $config;
    private ManagerRegistry $doctrine;
    private LoggerInterface $logger;

    public function __construct(ConfigManager $config, ManagerRegistry $doctrine, LoggerInterface $logger)
    {
        $this->config = $config;
        $this->doctrine = $doctrine;
        $this->logger = $logger;
    }

    public function onContentMatch(ContentMatchEvent $event): void
    {
        $contentContext = $event->getContentContext();
        $request = $event->getRequest();

        $route = $request->get('_route');
        if (!$this->isSupportedRoute($route)) {
            return;
        }

        $contentClass = $this->config->getContentClassForRoute($route);
        if ($contentClass === null) {
            $this->logger->warning(sprintf(
                'Could not match $mainContent on url "%s" and route "%s"',
                $request->getUri(),
                $route
            ));

            return;
        }

        $reflection = new ReflectionClass($contentClass);
        if ($this->config->getRouteSlugAndAction($route) === null
            || !$reflection->isSubclassOf(AbstractContent::class)
        ) {
            return;
        }

        try {
            /** @var DefaultTranslatableContentRepository $repository */
            $repository = $this->doctrine->getRepository($contentClass);
            if ($route === 'page_index') {
                $mainContent = $repository->contentFindOneBy(['_.homepage' => true]);
                if (empty($mainContent)) {
                    throw new Exception('You must define one page as "homepage".');
                }
                $contentContext->setMainContent($mainContent);
            } elseif (($routeConfig = $this->config->getRoute($route))
                && !empty($routeConfig['autofill_parameters'])
                && !empty($findParameters = $this->handleParameters(
                    $routeConfig['autofill_parameters'],
                    $request
                ))
            ) {
                $mainContent = $repository->contentFindOneBy($findParameters);
                if ($mainContent) {
                    $contentContext->setMainContent($mainContent);
                }
            }
        } catch (Exception $e) {
            $this->logger->error($e->getMessage());
        }
    }

    protected function isSupportedRoute($route): bool
    {
        if ($route === 'page_index') {
            return true;
        }

        return $this->config->hasRoute($route)
            && !empty($this->config->getRoute($route)['autofill_parameters']);
    }

    protected function handleParameters(array $autofillParameters, Request $request): array
    {
        $findParameters = [];
        foreach ($autofillParameters as $param => $path) {
            $tableDepthCounter = 0;

            // we shouldn't have this format here but replace to be safe
            $path = str_replace('currentTranslation.', '_tr.', $path);

            if (str_starts_with($path, '_.')) {
                $prefix = '_.';
            } elseif (str_starts_with($path, '_tr.')) {
                $prefix = '_tr.';
            } else {
                $prefix = '_.';// default to translatable alias
                $tableDepthCounter++;
            }

            $tableDepthCounter += substr_count($path, '_tr.');
            $tableDepthCounter += substr_count($path, '_.');

            if ($tableDepthCounter > 1) {
                // we don't want to handle only simple contexts, so only alias.field format,
                // as deeper paths will lead to missing doctrine joints
                // until this joints problems are handled, we reject complex cases
                // (leaving content matching to the controllers)
                break;
            }

            // here, prefix is table alias, param is first-level field name
            // (param is also used to get request route value and construct doctrine criteria)
            $findParameters[$prefix . $param] = $request->get($param);
        }

        return $findParameters;
    }
}
