<?php

declare(strict_types=1);

namespace Mautic\IntegrationsBundle\Controller;

use Mautic\CoreBundle\Controller\AbstractFormController;
use Mautic\CoreBundle\Twig\Extension\FormExtension;
use Mautic\IntegrationsBundle\Event\ConfigAuthUrlEvent;
use Mautic\IntegrationsBundle\Event\ConfigSaveEvent;
use Mautic\IntegrationsBundle\Event\FormLoadEvent;
use Mautic\IntegrationsBundle\Event\KeysSaveEvent;
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
use Mautic\IntegrationsBundle\Form\Type\IntegrationConfigType;
use Mautic\IntegrationsBundle\Helper\ConfigIntegrationsHelper;
use Mautic\IntegrationsBundle\Helper\FieldMergerHelper;
use Mautic\IntegrationsBundle\Helper\FieldValidationHelper;
use Mautic\IntegrationsBundle\Integration\BasicIntegration;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormAuthInterface;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormAuthorizeButtonInterface;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormCallbackInterface;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeatureSettingsInterface;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormFeaturesInterface;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormInterface;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\PluginBundle\Entity\Integration;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;

class ConfigController extends AbstractFormController
{
    /**
     * @var BasicIntegration|ConfigFormInterface
     */
    private $integrationObject;

    /**
     * @var Integration
     */
    private $integrationConfiguration;

    /**
     * @return array|JsonResponse|RedirectResponse|Response
     */
    public function editAction(
        Request $request,
        ConfigIntegrationsHelper $integrationsHelper,
        EventDispatcherInterface $dispatcher,
        FieldValidationHelper $fieldValidator,
        FormFactoryInterface $formFactory,
        FormExtension $formExtension,
        string $integration
    ) {
        // Check ACL
        if (!$this->security->isGranted('plugin:plugins:manage')) {
            return $this->accessDenied();
        }

        try {
            $this->integrationObject        = $integrationsHelper->getIntegration($integration);
            $this->integrationConfiguration = $this->integrationObject->getIntegrationConfiguration();
        } catch (IntegrationNotFoundException) {
            return $this->notFound();
        }

        $event = new FormLoadEvent($this->integrationConfiguration);
        $dispatcher->dispatch($event, IntegrationEvents::INTEGRATION_CONFIG_FORM_LOAD);

        // Create the form
        $form = $this->getForm($formFactory);

        if (Request::METHOD_POST === $request->getMethod()) {
            return $this->submitForm($request, $integrationsHelper, $fieldValidator, $dispatcher, $formFactory, $formExtension, $form);
        }

        // Clear the session of previously stored fields in case it got stuck
        /** @var Session $session */
        $session = $request->getSession();
        $session->remove("$integration-fields");

        return $this->showForm($request, $form, $formExtension);
    }

    /**
     * @param FormInterface<mixed> $form
     */
    private function submitForm(
        Request $request,
        ConfigIntegrationsHelper $integrationsHelper,
        FieldValidationHelper $fieldValidator,
        EventDispatcherInterface $eventDispatcher,
        FormFactoryInterface $formFactory,
        FormExtension $formExtension,
        FormInterface $form
    ): JsonResponse|Response {
        if ($this->isFormCancelled($form)) {
            return $this->closeForm($request);
        }

        // Get the fields before the form binds partial data due to pagination
        $settings          = $this->integrationConfiguration->getFeatureSettings();
        $fieldMappings     = $settings['sync']['fieldMappings'] ?? [];
        $oldApiKeys        = $this->integrationConfiguration->getApiKeys();

        // Submit the form
        $form->handleRequest($request);

        $configEvent = new KeysSaveEvent($this->integrationConfiguration, $oldApiKeys);
        $this->dispatcher->dispatch($configEvent, IntegrationEvents::INTEGRATION_API_KEYS_BEFORE_SAVE);

        if ($this->integrationObject instanceof ConfigFormSyncInterface) {
            $integration   = $this->integrationObject->getName();
            $settings      = $this->integrationConfiguration->getFeatureSettings();
            $session       = $request->getSession();
            $updatedFields = $session->get("$integration-fields", []);

            $fieldMerger = new FieldMergerHelper($this->integrationObject, $fieldMappings);

            foreach ($updatedFields as $object => $fields) {
                $fieldMerger->mergeSyncFieldMapping($object, $fields);
            }

            $settings['sync']['fieldMappings'] = $fieldMerger->getFieldMappings();

            $fieldValidator->validateRequiredFields($form, $this->integrationObject, $settings['sync']['fieldMappings']);

            $this->integrationConfiguration->setFeatureSettings($settings);
        }

        // Dispatch event prior to saving the Integration. Bundles/plugins may need to modify some field values before save
        $configEvent = new ConfigSaveEvent($this->integrationConfiguration);
        $eventDispatcher->dispatch($configEvent, IntegrationEvents::INTEGRATION_CONFIG_BEFORE_SAVE);

        // Show the form if there are errors and the plugin is published or the authorized button was clicked
        $integrationDetailsPost = $request->request->get('integration_details') ?? [];
        $authorize              = !empty($integrationDetailsPost['in_auth']);
        if ($form->isSubmitted() && !$form->isValid() && ($this->integrationConfiguration->getIsPublished() || $authorize)) {
            return $this->showForm($request, $form, $formExtension);
        }

        // Save the integration configuration
        $integrationsHelper->saveIntegrationConfiguration($this->integrationConfiguration);

        // Dispatch after save event
        $eventDispatcher->dispatch($configEvent, IntegrationEvents::INTEGRATION_CONFIG_AFTER_SAVE);

        // Show the form if the apply button was clicked
        if ($this->isFormApplied($form)) {
            // Regenerate the form
            $this->resetFieldsInSession($request);
            $form = $this->getForm($formFactory);

            return $this->showForm($request, $form, $formExtension);
        }

        // Otherwise close the modal
        return $this->closeForm($request);
    }

    /**
     * @return FormInterface<mixed>
     */
    private function getForm(FormFactoryInterface $formFactory)
    {
        return $formFactory->create(
            $this->integrationObject->getConfigFormName() ?: IntegrationConfigType::class,
            $this->integrationConfiguration,
            [
                'action'      => $this->generateUrl('mautic_integration_config', ['integration' => $this->integrationObject->getName()]),
                'integration' => $this->integrationObject->getName(),
            ]
        );
    }

    /**
     * @param FormInterface<mixed> $form
     */
    private function showForm(Request $request, FormInterface $form, FormExtension $formExtension): Response
    {
        $integrationObject = $this->integrationObject;
        $formView          = $form->createView();

        $showFeaturesTab = $integrationObject instanceof ConfigFormFeaturesInterface
            || $integrationObject instanceof ConfigFormSyncInterface
            || $integrationObject instanceof ConfigFormFeatureSettingsInterface;

        $hasFeatureErrors = (
            $integrationObject instanceof ConfigFormFeatureSettingsInterface
            && $formExtension->containsErrors($formView['featureSettings']['integration'])
        ) || (
            isset($formView['featureSettings']['sync']['integration'])
            && $formExtension->containsErrors($formView['featureSettings']['sync']['integration'])
        );

        $hasAuthErrors = $integrationObject instanceof ConfigFormAuthInterface && $formExtension->containsErrors($formView['apiKeys']);

        $useSyncFeatures = $integrationObject instanceof ConfigFormSyncInterface;

        $useFeatureSettings = $integrationObject instanceof ConfigFormFeatureSettingsInterface;

        $useAuthorizationUrl = $integrationObject instanceof ConfigFormAuthorizeButtonInterface;

        $callbackUrl = $integrationObject instanceof ConfigFormCallbackInterface ?
            $integrationObject->getRedirectUri()
            : false;

        $useConfigFormNotes = $integrationObject instanceof ConfigFormNotesInterface;

        return $this->delegateView(
            [
                'viewParameters' => [
                    'integrationObject'   => $integrationObject,
                    'form'                => $formView,
                    'activeTab'           => $request->get('activeTab'),
                    'showFeaturesTab'     => $showFeaturesTab,
                    'hasFeatureErrors'    => $hasFeatureErrors,
                    'hasAuthErrors'       => $hasAuthErrors,
                    'useSyncFeatures'     => $useSyncFeatures,
                    'useFeatureSettings'  => $useFeatureSettings,
                    'useAuthorizationUrl' => $useAuthorizationUrl,
                    'callbackUrl'         => $callbackUrl,
                    'useConfigFormNotes'  => $useConfigFormNotes,
                ],
                'contentTemplate' => $integrationObject->getConfigFormContentTemplate()
                    ?: '@Integrations/Config/form.html.twig',
                'passthroughVars' => [
                    'activeLink'    => '#mautic_plugin_index',
                    'mauticContent' => 'integrationsConfig',
                    'route'         => false,
                ],
            ]
        );
    }

    private function closeForm(Request $request): JsonResponse
    {
        $this->resetFieldsInSession($request);

        $response = [
            'closeModal'    => 1,
            'enabled'       => $this->integrationConfiguration->getIsPublished(),
            'name'          => $this->integrationConfiguration->getName(),
            'mauticContent' => 'integrationsConfig',
            'flashes'       => $this->getFlashContent(),
        ];

        if ($this->integrationObject instanceof ConfigFormAuthorizeButtonInterface) {
            // Dispatch event to allow listeners to extract information and/or manipulate the URL
            $authUrl      = $this->integrationObject->getAuthorizationUrl();
            $authUrlEvent = new ConfigAuthUrlEvent($this->integrationConfiguration, $authUrl);
            $this->dispatcher->dispatch($authUrlEvent, IntegrationEvents::INTEGRATION_CONFIG_ON_GENERATE_AUTH_URL);

            $response['authUrl'] = $authUrlEvent->getAuthUrl();
        }

        return new JsonResponse($response);
    }

    private function resetFieldsInSession(Request $request): void
    {
        $session = $request->getSession();
        $session->remove("{$this->integrationObject->getName()}-fields");
    }
}
