<?php

namespace AutoAdmin\Bundle\Admin\Controller\Admin;

use AutoAdmin\Bundle\Admin\Controller\Admin\Traits\AbstractAdminControllerContainerTrait;
use AutoAdmin\Bundle\Admin\Exception\ForbiddenActionException;
use AutoAdmin\Bundle\Admin\Exception\ForbiddenSiteException;
use AutoAdmin\Bundle\Admin\Exception\UnknownEntityException;
use AutoAdmin\Bundle\Admin\Model\AdministratorInterface;
use AutoAdmin\Bundle\Content\Entity\Site;
use AutoAdmin\Bundle\Content\Model\SiteInterface;
use Doctrine\ORM\EntityNotFoundException;
use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController as BaseAbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;

abstract class AbstractAdminController extends BaseAbstractController implements AdminControllerInterface
{
    use AbstractAdminControllerContainerTrait;

    public function ensureActionIsAllowed(string $entity, string $actionName): bool
    {
        $entityFqcn = $this->entityHelper->guessFqcn($entity);
        $isGranted = $this->accessHelper->isActionAllowed($entityFqcn, $actionName);
        if (!$isGranted) {
            throw new ForbiddenActionException($actionName);
        }

        return $isGranted;
    }

    public function ensureControllerActionIsAllowed(string $controllerFqcn, string $actionName): bool
    {
        $isGranted = $this->accessHelper->isActionAllowed($controllerFqcn, $actionName);
        if (!$isGranted) {
            throw new ForbiddenActionException($actionName);
        }

        return $isGranted;
    }

    public function __call($name, $arguments)
    {
        throw new Exception(sprintf('Action "%s" of controller "%s" is not implemented.', $name, get_class($this)));
    }

    protected function resolveEntityFromRequest(Request $request)
    {
        return $this->resolveEntity($request->get('entitySlug'), $request->get('id'));
    }

    protected function getEntityConfigFromRequest(Request $request): ?array
    {
        $config = null;
        $class = $this->entityHelper->findFullClassName($entitySlug = $request->get('entitySlug'));
        if ($class) {
            $config = $this->configManager->getEntityConfigByClass($class);
        }

        return $config;
    }

    /**
     * @return EntityNotFoundException|object|null
     * @throws UnknownEntityException
     */
    protected function resolveEntity(string $entitySlug, int $id, bool $ensureMatchingSite = true)
    {
        $class = $this->entityHelper->findFullClassName($entitySlug);

        if (!$class) {
            throw new UnknownEntityException($entitySlug);
        }

        if (empty($id)) {
            throw EntityNotFoundException::fromClassNameAndIdentifier($class, [$id]);
        }

        $entity = $this->entityHelper->getManager()->getRepository($class)->find($id);

        if (!$entity) {
            throw EntityNotFoundException::fromClassNameAndIdentifier($class, [$id]);
        }

        /** @var AdministratorInterface $administrator */
        if (
            $ensureMatchingSite
            && $entity instanceof SiteInterface
            && ($administrator = $this->getUser())
            && !$this->supportEntitySite($administrator, $entity)
        ) {
            throw new ForbiddenSiteException($entity->getSite()->getTitle());
        }

        return $entity;
    }

    protected function getAdminUrl(object $object, string $action = 'view', array $parameters = []): string
    {
        return $this->adminRouter->generate($object, $action, $parameters);
    }

    protected function supportEntitySite(AdministratorInterface $administrator, SiteInterface $entity): bool
    {
        if (empty($entity->getSite())) {
            return false;
        }

        /** @var Site[] $sites */
        $sites = $administrator->getAdministratorSites()->toArray();
        foreach ($sites as $site) {
            if ($site->getId() === $entity->getSite()->getId()) {
                return true;
            }
        }

        return false;
    }

    public function redirectToCurrent(Request $request): RedirectResponse
    {
        $url = $this->generateUrl(
            $request->attributes->get('_route'),
            array_merge(
                $request->attributes->get('_route_params'),
                $request->query->all()
            )
        );

        return $this->redirect($url);
    }
}
