<?php

namespace Mautic\FormBundle\Controller;

use Mautic\CoreBundle\Controller\FormController as CommonFormController;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\CoreBundle\Helper\ThemeHelper;
use Mautic\CoreBundle\Twig\Helper\AnalyticsHelper;
use Mautic\CoreBundle\Twig\Helper\AssetsHelper;
use Mautic\CoreBundle\Twig\Helper\DateHelper;
use Mautic\FormBundle\Event\SubmissionEvent;
use Mautic\FormBundle\Model\FieldModel;
use Mautic\FormBundle\Model\FormModel;
use Mautic\FormBundle\Model\SubmissionModel;
use Mautic\LeadBundle\Helper\TokenHelper;
use Mautic\LeadBundle\Model\CompanyModel;
use Mautic\PageBundle\Helper\TokenHelper as PageTokenHelper;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class PublicController extends CommonFormController
{
    private array $tokens = [];

    /**
     * @return RedirectResponse|Response
     */
    public function submitAction(Request $request, DateHelper $dateTemplateHelper, PageTokenHelper $pageTokenHelper)
    {
        if ('POST' !== $request->getMethod()) {
            return $this->accessDenied();
        }
        $isAjax        = $request->query->get('ajax');
        $form          = null;
        $post          = $request->request->all()['mauticform'] ?? [];
        $messengerMode = (!empty($post['messenger']));
        $server        = $request->server->all();
        $return        = $post['return'] ?? false;

        if (empty($return)) {
            // try to get it from the HTTP_REFERER
            $return = $server['HTTP_REFERER'] ?? false;
        }

        if (!empty($return)) {
            // remove mauticError and mauticMessage from the referer so it doesn't get sent back
            $return = InputHelper::url($return, null, null, null, ['mauticError', 'mauticMessage'], true);
            $query  = (!str_contains($return, '?')) ? '?' : '&';
        }

        $translator = $this->translator;

        // check to ensure there is a formId
        if (!isset($post['formId'])) {
            $error = $translator->trans('mautic.form.submit.error.unavailable', [], 'flashes');
        } else {
            $formModel = $this->getModel('form.form');
            $form      = $formModel->getEntity($post['formId']);

            // check to see that the form was found
            if (null === $form) {
                $error = $translator->trans('mautic.form.submit.error.unavailable', [], 'flashes');
            } else {
                // get what to do immediately after successful post
                $postAction         = $form->getPostAction();
                $postActionProperty = $form->getPostActionProperty();

                // check to ensure the form is published
                $status             = $form->getPublishStatus();
                if ('pending' == $status) {
                    $error = $translator->trans(
                        'mautic.form.submit.error.pending',
                        [
                            '%date%' => $dateTemplateHelper->toFull($form->getPublishUp()),
                        ],
                        'flashes'
                    );
                } elseif ('expired' == $status) {
                    $error = $translator->trans(
                        'mautic.form.submit.error.expired',
                        [
                            '%date%' => $dateTemplateHelper->toFull($form->getPublishDown()),
                        ],
                        'flashes'
                    );
                } elseif ('published' != $status) {
                    $error = $translator->trans('mautic.form.submit.error.unavailable', [], 'flashes');
                } else {
                    $formSubmissionModel = $this->getModel('form.submission');
                    \assert($formSubmissionModel instanceof SubmissionModel);
                    $result = $formSubmissionModel->saveSubmission($post, $server, $form, $request, true);
                    if (!empty($result['errors'])) {
                        if ($messengerMode || $isAjax) {
                            $error = $result['errors'];
                        } else {
                            $error = ($result['errors']) ?
                                $this->translator->trans('mautic.form.submission.errors').'<br /><ol><li>'.
                                implode('</li><li>', $result['errors']).'</li></ol>' : false;
                        }
                    } elseif (!empty($result['callback'])) {
                        /** @var SubmissionEvent $submissionEvent */
                        $submissionEvent   = $result['callback'];
                        $callbackResponses = $submissionEvent->getPostSubmitCallbackResponse();
                        // These submit actions have requested a callback after all is said and done
                        $callbacksRequested = $submissionEvent->getPostSubmitCallback();
                        foreach ($callbacksRequested as $key => $callbackRequested) {
                            $callbackRequested['messengerMode'] = $messengerMode;
                            $callbackRequested['ajaxMode']      = $isAjax;

                            if (isset($callbackRequested['eventName'])) {
                                $submissionEvent->setPostSubmitCallback($key, $callbackRequested);
                                $submissionEvent->setContext($key);

                                $this->dispatcher->dispatch($submissionEvent, $callbackRequested['eventName']);
                            }

                            if ($submissionEvent->isPropagationStopped() && $submissionEvent->hasPostSubmitResponse()) {
                                if ($messengerMode) {
                                    $callbackResponses[$key] = $submissionEvent->getPostSubmitResponse();
                                } else {
                                    return $submissionEvent->getPostSubmitResponse();
                                }
                            }
                        }
                    } elseif (isset($result['submission'])) {
                        /** @var SubmissionEvent $submissionEvent */
                        $submissionEvent = $result['submission'];
                    }
                }
            }
        }

        if (isset($submissionEvent) && !empty($postActionProperty)) {
            // Replace post action property with tokens to support custom redirects, etc
            $postActionProperty = $this->replacePostSubmitTokens($postActionProperty, $submissionEvent, $pageTokenHelper);
        }

        if ($messengerMode || $isAjax) {
            // Return the call via postMessage API
            $data = ['success' => 1];
            if (!empty($error)) {
                if (is_array($error)) {
                    $data['validationErrors'] = $error;
                } else {
                    $data['errorMessage'] = $error;
                }
                $data['success'] = 0;
            } else {
                // Include results in ajax response for JS callback use
                if (isset($submissionEvent)) {
                    $data['results'] = $submissionEvent->getResults();
                }

                switch ($postAction) {
                    case 'redirect':
                        $data['redirect'] = $postActionProperty;
                        break;
                    case 'hideform':
                        $data['hideform'] = true;
                        // no break
                    default:
                        if (!empty($postActionProperty)) {
                            $data['successMessage'] = [$postActionProperty];
                        }
                        break;
                }

                if (!empty($callbackResponses)) {
                    foreach ($callbackResponses as $response) {
                        // Convert the responses to something useful for a JS response
                        if ($response instanceof RedirectResponse && !isset($data['redirect'])) {
                            $data['redirect'] = $response->getTargetUrl();
                        } elseif ($response instanceof Response) {
                            if (!isset($data['successMessage'])) {
                                $data['successMessage'] = [];
                            }
                            $data['successMessage'][] = $response->getContent();
                        } elseif (is_array($response)) {
                            $data = array_merge($data, $response);
                        } elseif (is_string($response)) {
                            if (!isset($data['successMessage'])) {
                                $data['successMessage'] = [];
                            }
                            $data['successMessage'][] = $response;
                        } // ignore anything else
                    }
                }

                // Combine all messages into one
                if (isset($data['successMessage'])) {
                    $data['successMessage'] = implode('<br /><br />', $data['successMessage']);
                }
            }

            if (isset($post['formName'])) {
                $data['formName'] = $post['formName'];
            }

            if ($isAjax) {
                // Post via ajax so return a json response
                return new JsonResponse($data);
            } else {
                $response = json_encode($data);

                return $this->render('@MauticForm/messenger.html.twig', ['response' => $response]);
            }
        } else {
            if (!empty($error)) {
                if ($return) {
                    $hash = (null !== $form) ? '#'.strtolower($form->getAlias()) : '';

                    return $this->redirect($return.$query.'mauticError='.rawurlencode($error).$hash);
                } else {
                    $msg     = $error;
                    $msgType = 'error';
                }
            } elseif ('redirect' == $postAction) {
                return $this->redirect($postActionProperty);
            } elseif ('return' == $postAction) {
                if (!empty($return)) {
                    if (!empty($postActionProperty)) {
                        $return .= $query.'mauticMessage='.rawurlencode($postActionProperty);
                    }

                    return $this->redirect($return);
                } else {
                    $msg = $this->translator->trans('mautic.form.submission.thankyou');
                }
            } else {
                $msg = $postActionProperty;
            }

            $session = $request->getSession();
            $session->set(
                'mautic.emailbundle.message',
                [
                    'message' => $msg,
                    'type'    => (empty($msgType)) ? 'notice' : $msgType,
                ]
            );

            return $this->redirectToRoute('mautic_form_postmessage');
        }
    }

    /**
     * Displays a message.
     */
    public function messageAction(Request $request, AnalyticsHelper $analyticsHelper, AssetsHelper $assetsHelper, ThemeHelper $themeHelper): Response
    {
        $session = $request->getSession();
        $message = $session->get('mautic.emailbundle.message', []);

        $msg     = (!empty($message['message'])) ? $message['message'] : '';
        $msgType = (!empty($message['type'])) ? $message['type'] : 'notice';

        $analytics = $analyticsHelper->getCode();

        if (!empty($analytics)) {
            $assetsHelper->addCustomDeclaration($analytics);
        }

        $logicalName = $themeHelper->checkForTwigTemplate('@themes/'.$this->coreParametersHelper->get('theme').'/html/message.html.twig');

        return $this->render($logicalName, [
            'message'  => $msg,
            'type'     => $msgType,
            'template' => $this->coreParametersHelper->get('theme'),
        ]);
    }

    /**
     * Gives a preview of the form.
     *
     * @return Response
     *
     * @throws \Exception
     * @throws \Mautic\CoreBundle\Exception\FileNotFoundException
     */
    public function previewAction(Request $request, AnalyticsHelper $analyticsHelper, AssetsHelper $assetsHelper, ThemeHelper $themeHelper, int $id = 0)
    {
        $model = $this->getModel('form.form');
        \assert($model instanceof FormModel);
        $objectId          = (empty($id)) ? (int) $request->get('id') : $id;
        $css               = InputHelper::string((string) $request->get('css'));
        $form              = $model->getEntity($objectId);
        $customStylesheets = (!empty($css)) ? explode(',', $css) : [];
        $template          = null;

        if (null === $form || !$form->isPublished()) {
            return $this->notFound();
        } else {
            $html = $model->getContent($form);

            $model->populateValuesWithGetParameters($form, $html);

            $viewParams = [
                'content'     => $html,
                'stylesheets' => $customStylesheets,
                'name'        => $form->getName(),
                'metaRobots'  => '<meta name="robots" content="index">',
            ];

            if ($form->getNoIndex()) {
                $viewParams['metaRobots'] = '<meta name="robots" content="noindex">';
            }

            // Use form specific template or system-wide default theme
            $template = $form->getTemplate() ?? $this->coreParametersHelper->get('theme');
            if (!empty($template)) {
                $theme = $themeHelper->getTheme($template);
                if ($theme->getTheme() != $template) {
                    $config = $theme->getConfig();
                    if (in_array('form', $config['features'])) {
                        $template = $theme->getTheme();
                    } else {
                        $template = null;
                    }
                }
            }
        }

        $viewParams['template'] = $template;

        if (!empty($template)) {
            $logicalName  = $themeHelper->checkForTwigTemplate('@themes/'.$template.'/html/form.html.twig');
            $analytics    = $analyticsHelper->getCode();

            foreach ($customStylesheets as $css) {
                $assetsHelper->addStylesheet($css);
            }

            if (!empty($analytics)) {
                $assetsHelper->addCustomDeclaration($analytics);
            }
            if ($form->getNoIndex()) {
                $assetsHelper->addCustomDeclaration('<meta name="robots" content="noindex">');
            }

            return $this->render($logicalName, $viewParams);
        }

        return $this->render('@MauticForm/form.html.twig', $viewParams);
    }

    /**
     * Generates JS file for automatic form generation.
     */
    public function generateAction(Request $request): Response
    {
        // Don't store a visitor with this request
        defined('MAUTIC_NON_TRACKABLE_REQUEST') || define('MAUTIC_NON_TRACKABLE_REQUEST', 1);

        $formId = (int) $request->get('id');

        $model = $this->getModel('form.form');
        \assert($model instanceof FormModel);
        $form  = $model->getEntity($formId);
        $js    = '';

        if (null !== $form) {
            $status = $form->getPublishStatus();
            if ('published' === $status) {
                $js = $model->getAutomaticJavascript($form);
            }
        }

        $response = new Response();
        $response->setContent($js);
        $response->setStatusCode(Response::HTTP_OK);
        $response->headers->set('Content-Type', 'text/javascript');

        return $response;
    }

    /**
     * @return Response
     */
    public function embedAction(Request $request)
    {
        $formId = (int) $request->get('id');
        /** @var FormModel $model */
        $model = $this->getModel('form');
        $form  = $model->getEntity($formId);

        if (null !== $form) {
            $status = $form->getPublishStatus();
            if ('published' === $status) {
                if ($request->get('video')) {
                    return $this->render(
                        '@MauticForm/Public/videoembed.html.twig',
                        ['form' => $form, 'fieldSettings' => $model->getCustomComponents()['fields']]
                    );
                }

                $content = $model->getContent($form, false, true);

                return new Response($content);
            }
        }

        return new Response('', Response::HTTP_NOT_FOUND);
    }

    /**
     * @return string|string[]
     */
    private function replacePostSubmitTokens($string, SubmissionEvent $submissionEvent, PageTokenHelper $pageTokenHelper): string|array
    {
        if (count($this->tokens)) {
            return $this->tokens;
        }

        if ($lead = $submissionEvent->getLead()) {
            $this->tokens = array_merge(
                $submissionEvent->getTokens(),
                TokenHelper::findLeadTokens(
                    $string,
                    $lead->getProfileFields()
                )
            );
        }

        $this->tokens = array_merge(
            $this->tokens,
            $pageTokenHelper->findPageTokens($string)
        );

        return str_replace(array_keys($this->tokens), array_values($this->tokens), $string);
    }

    public function lookupCompanyAction(Request $request, FieldModel $fieldModel, CompanyModel $companyModel): JsonResponse
    {
        $parameters = json_decode($request->getContent(), true);
        $search     = InputHelper::clean($parameters['search'] ?? '');
        $formId     = (int) ($parameters['formId'] ?? 0);

        // Intentionally vague message as the JS takes care of this.
        // Make it hard to abuse this public endpoint.
        $vagueErrorMessage = ['error' => 'Invalid request param'];

        if (mb_strlen($search) < 3 || !$formId) {
            return new JsonResponse($vagueErrorMessage, JsonResponse::HTTP_BAD_REQUEST);
        }

        if (!$fieldModel->getRepository()->fieldExistsByFormAndType($formId, 'companyLookup')) {
            return new JsonResponse($vagueErrorMessage, JsonResponse::HTTP_BAD_REQUEST);
        }

        return new JsonResponse($companyModel->getRepository()->getCompanyLookupData($search));
    }
}
