<?php

namespace Mautic\CoreBundle\Controller;

use Symfony\Component\Form\ClickableInterface;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;

abstract class AbstractFormController extends CommonController
{
    protected ?string $permissionBase = null;

    /**
     * @return mixed
     */
    public function unlockAction(Request $request, $objectId, $objectModel)
    {
        $model                = $this->getModel($objectModel);
        $entity               = $model->getEntity($objectId);
        $this->permissionBase = $model->getPermissionBase();

        if ($this->canEdit($entity)) {
            if (null !== $entity && null !== $entity->getCheckedOutBy()) {
                $model->unlockEntity($entity);
            }

            $currentUrl = $request->getUri();
            $returnUrl  = urldecode($request->get('returnUrl'));

            if (!filter_var($returnUrl, FILTER_VALIDATE_URL)) {
                $returnUrl = $this->generateUrl('mautic_dashboard_index');
            } else {
                $currentHost = parse_url($currentUrl, PHP_URL_HOST);
                $returnHost  = parse_url($returnUrl, PHP_URL_HOST);

                if ($currentHost !== $returnHost) {
                    $returnUrl = $this->generateUrl('mautic_dashboard_index');
                }
            }

            $this->addFlashMessage(
                'mautic.core.action.entity.unlocked',
                [
                    '%name%' => htmlspecialchars(urldecode($request->get('name')), ENT_QUOTES, 'UTF-8'),
                ]
            );

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

        return $this->accessDenied();
    }

    /**
     * Returns view to index with a locked out message.
     *
     * @param array  $postActionVars
     * @param object $entity
     * @param string $model
     * @param bool   $batch          Flag if a batch action is being performed
     *
     * @return \Symfony\Component\HttpFoundation\JsonResponse|RedirectResponse|array
     */
    protected function isLocked($postActionVars, $entity, $model, $batch = false)
    {
        $date                   = $entity->getCheckedOut();
        $postActionVars         = $this->refererPostActionVars($postActionVars);
        $returnUrl              = $postActionVars['returnUrl'];
        $override               = '';

        $modelClass             = $this->getModel($model);
        $nameFunction           = $modelClass->getNameGetter();
        $this->permissionBase   = $modelClass->getPermissionBase();

        if ($this->canEdit($entity)) {
            $override = $this->translator->trans(
                'mautic.core.override.lock',
                [
                    '%url%' => $this->generateUrl(
                        'mautic_core_form_action',
                        [
                            'objectAction' => 'unlock',
                            'objectModel'  => $model,
                            'objectId'     => $entity->getId(),
                            'returnUrl'    => $returnUrl,
                            'name'         => urlencode($entity->$nameFunction()),
                        ]
                    ),
                ]
            );
        }

        $flash = [
            'type'    => 'error',
            'msg'     => 'mautic.core.error.locked',
            'msgVars' => [
                '%name%'       => $entity->$nameFunction(),
                '%user%'       => $entity->getCheckedOutByUser(),
                '%contactUrl%' => $this->generateUrl(
                    'mautic_user_action',
                    [
                        'objectAction' => 'contact',
                        'objectId'     => $entity->getCheckedOutBy(),
                        'entity'       => $model,
                        'id'           => $entity->getId(),
                        'subject'      => 'locked',
                        'returnUrl'    => $returnUrl,
                    ]
                ),
                '%date%'     => $date->format($this->coreParametersHelper->get('date_format_dateonly')),
                '%time%'     => $date->format($this->coreParametersHelper->get('date_format_timeonly')),
                '%datetime%' => $date->format($this->coreParametersHelper->get('date_format_full')),
                '%override%' => $override,
            ],
        ];

        if ($batch) {
            return $flash;
        }

        return $this->postActionRedirect(
            array_merge(
                $postActionVars,
                [
                    'flashes' => [$flash],
                ]
            )
        );
    }

    /**
     * Checks to see if the form was cancelled.
     */
    protected function isFormCancelled(FormInterface $form): bool
    {
        $request = $this->getCurrentRequest();
        if (null === $request) {
            throw new \RuntimeException('Request is required.');
        }

        $formData = $request->request->all()[$form->getName()] ?? [];

        return is_array($formData) && array_key_exists('buttons', $formData) && array_key_exists('cancel', $formData['buttons']);
    }

    /**
     * Checks to see if the form was applied or saved.
     */
    protected function isFormApplied(FormInterface $form): bool
    {
        $request = $this->getCurrentRequest();
        if (null === $request) {
            throw new \RuntimeException('Request is required.');
        }

        $formData = $request->request->all()[$form->getName()] ?? [];

        return array_key_exists('buttons', $formData) && array_key_exists('apply', $formData['buttons']);
    }

    /**
     * Binds form data, checks validity, and determines cancel request.
     */
    protected function isFormValid(FormInterface $form): bool
    {
        $request = $this->getCurrentRequest();
        if (null === $request) {
            throw new \RuntimeException('Request is required.');
        }

        // bind request to the form
        $form->handleRequest($request);

        return $form->isSubmitted() && $form->isValid();
    }

    /**
     * Decide if current user can edit or can edit specific entity if entity is provided
     * For BC, if permissionBase property is not set, it allow to edit only to administrators.
     *
     * @param object $entity
     *
     * @return bool
     */
    protected function canEdit($entity = null)
    {
        if ($this->permissionBase) {
            $permissionBase = $this->permissionBase;
        } else {
            $permissionBase = $this->getPermissionBase();
        }

        if ($permissionBase) {
            if ($entity && $this->security->checkPermissionExists($permissionBase.':editown')) {
                return $this->security->hasEntityAccess(
                    $permissionBase.':editown',
                    $permissionBase.':editother',
                    $entity->getCreatedBy()
                );
            } elseif ($this->security->checkPermissionExists($permissionBase.':edit')) {
                return $this->security->isGranted(
                    $permissionBase.':edit'
                );
            }
        }

        return $this->user->isAdmin();
    }

    protected function copyErrorsRecursively(FormInterface $copyFrom, FormInterface $copyTo)
    {
        foreach ($copyFrom->getErrors() as $error) {
            $copyTo->addError($error);
        }

        foreach ($copyFrom->all() as $key => $child) {
            if ($child instanceof Form && $copyTo->has($key)) {
                $childTo = $copyTo->get($key);
                $this->copyErrorsRecursively($child, $childTo);
            }
        }
    }

    /**
     * generate $postActionVars with respect to available referer.
     *
     * @return array $postActionVars
     */
    protected function refererPostActionVars($vars)
    {
        $request = $this->getCurrentRequest();
        if (null === $request) {
            throw new \RuntimeException('Request is required.');
        }

        if (empty($request->server->get('HTTP_REFERER'))) {
            return $vars;
        }

        $returnUrl         = !empty($request->server->get('HTTP_REFERER')) ? $request->server->get('HTTP_REFERER') : '';
        $vars['returnUrl'] = $returnUrl;

        $urlMatcher  = explode('/s/', $returnUrl);
        $actionRoute = $this->container->get('router')->match('/s/'.$urlMatcher[1]);
        $objAction   = $actionRoute['objectAction'] ?? 'index';
        $routeCtrlr  = explode('\\', $actionRoute['_controller']);

        $defaultContentTemplate  = $routeCtrlr[0].$routeCtrlr[1].':'.ucfirst(str_replace('Bundle', '', $routeCtrlr[1])).':'.$objAction;
        $vars['contentTemplate'] ??= $defaultContentTemplate;

        $vars['passthroughVars']['activeLink'] = '#'.str_replace('_action', '_'.$objAction, $actionRoute['_route']);

        if (isset($actionRoute['objectId']) && $actionRoute['objectId'] > 0) {
            $vars['viewParameters']['objectId'] = $actionRoute['objectId'];
        }

        return $vars;
    }

    protected function getFormButton(FormInterface $form, array $elements): ClickableInterface
    {
        foreach ($elements as $element) {
            $form = $form->get($element);
        }

        \assert($form instanceof ClickableInterface);

        return $form;
    }

    protected function isButtonClicked(FormInterface $form, string $name): bool
    {
        return $form->get('buttons')->has($name) && $this->getFormButton($form, ['buttons', $name])->isClicked();
    }

    /**
     * @param string[] $names
     */
    protected function isAnyOfButtonsClicked(FormInterface $form, array $names): bool
    {
        foreach ($names as $name) {
            if ($this->isButtonClicked($form, $name)) {
                return true;
            }
        }

        return false;
    }
}
