<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Controller;

use Doctrine\ORM\EntityManagerInterface;
use Mautic\CoreBundle\Controller\CommonController;
use MauticPlugin\DealFlowBundle\Entity\DealField;
use MauticPlugin\DealFlowBundle\Entity\DealFieldRepository;
use MauticPlugin\DealFlowBundle\Form\Type\DealFieldType;
use MauticPlugin\DealFlowBundle\Service\DealAuthorization;
use MauticPlugin\DealFlowBundle\Service\DealFieldManager;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

final class DealFieldController extends CommonController
{
    public function indexAction(
        DealAuthorization $authorization,
        DealFieldRepository $dealFieldRepository,
    ): Response {
        if (!$authorization->canViewSettings()) {
            return $this->accessDenied();
        }

        return $this->delegateView([
            'viewParameters' => [
                'items' => $dealFieldRepository->findOrdered(),
                'canEditSettings' => $authorization->canEditSettings(),
            ],
            'contentTemplate' => '@DealFlow/DealField/list.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_field_index',
                'route' => $this->generateUrl('mautic_dealflow_field_index'),
                'mauticContent' => 'dealflow',
            ],
        ]);
    }

    public function newAction(
        Request $request,
        DealAuthorization $authorization,
        FormFactoryInterface $formFactory,
        EntityManagerInterface $entityManager,
        DealFieldRepository $dealFieldRepository,
        DealFieldManager $dealFieldManager,
    ): Response {
        if (!$authorization->canEditSettings()) {
            return $this->accessDenied();
        }

        $field = new DealField();
        $field->setSortOrder($dealFieldRepository->getNextSortOrder())
            ->setIsActive(true);

        return $this->handleFieldForm(
            $request,
            $formFactory,
            $entityManager,
            $dealFieldRepository,
            $dealFieldManager,
            $field,
            'Create deal field',
            'mautic_dealflow_field_new'
        );
    }

    public function editAction(
        int $objectId,
        Request $request,
        DealAuthorization $authorization,
        FormFactoryInterface $formFactory,
        EntityManagerInterface $entityManager,
        DealFieldRepository $dealFieldRepository,
        DealFieldManager $dealFieldManager,
    ): Response {
        if (!$authorization->canEditSettings()) {
            return $this->accessDenied();
        }

        $field = $dealFieldRepository->find($objectId);
        if (!$field instanceof DealField) {
            throw $this->createNotFoundException(sprintf('Deal field %d not found.', $objectId));
        }

        return $this->handleFieldForm(
            $request,
            $formFactory,
            $entityManager,
            $dealFieldRepository,
            $dealFieldManager,
            $field,
            'Edit deal field',
            'mautic_dealflow_field_edit'
        );
    }

    public function deleteAction(
        int $objectId,
        Request $request,
        DealAuthorization $authorization,
        EntityManagerInterface $entityManager,
        DealFieldRepository $dealFieldRepository,
        DealFieldManager $dealFieldManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        if (!$authorization->canEditSettings()) {
            return $this->accessDenied();
        }

        $field = $dealFieldRepository->find($objectId);
        if (!$field instanceof DealField) {
            throw $this->createNotFoundException(sprintf('Deal field %d not found.', $objectId));
        }

        if ($request->isMethod('POST')) {
            $this->validateCsrfToken(
                $csrfTokenManager,
                sprintf('dealflow_field_delete_%d', $field->getId()),
                (string) $request->request->get('_token')
            );

            $dealFieldManager->removeFieldFromDeals($field);
            $entityManager->remove($field);
            $entityManager->flush();

            $this->addFlash('notice', 'Deal field deleted.');

            return $this->delegateRedirect($this->generateUrl('mautic_dealflow_field_index'));
        }

        return $this->delegateView([
            'viewParameters' => [
                'field' => $field,
                'cancelUrl' => $this->generateUrl('mautic_dealflow_field_index'),
            ],
            'contentTemplate' => '@DealFlow/DealField/delete.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_field_index',
                'route' => $this->generateUrl('mautic_dealflow_field_delete', ['objectId' => $field->getId()]),
                'mauticContent' => 'dealflow',
            ],
        ]);
    }

    private function handleFieldForm(
        Request $request,
        FormFactoryInterface $formFactory,
        EntityManagerInterface $entityManager,
        DealFieldRepository $dealFieldRepository,
        DealFieldManager $dealFieldManager,
        DealField $field,
        string $pageTitle,
        string $routeName,
    ): Response {
        $isModal = $this->isModalRequest($request);
        $returnUrl = $this->resolveReturnUrl($request, $this->generateUrl('mautic_dealflow_field_index'));
        $form = $formFactory->create(DealFieldType::class, $field);
        $fieldBuilder = $this->buildFieldBuilderState($field);
        $routeParameters = [];
        if ('mautic_dealflow_field_edit' === $routeName && null !== $field->getId()) {
            $routeParameters['objectId'] = $field->getId();
        }

        $formAction = $this->generateUrl($routeName, array_filter(array_merge(
            $routeParameters,
            [
                'returnUrl' => $returnUrl,
                'modal' => $isModal ? 1 : null,
                'contentOnly' => $isModal ? 1 : null,
            ]
        ), static fn (mixed $value): bool => null !== $value && '' !== $value));
        $form->handleRequest($request);

        if ($form->isSubmitted()) {
            $field->setAlias($dealFieldManager->normalizeAlias('' !== $field->getAlias() ? $field->getAlias() : $field->getLabel()))
                ->setUpdatedAt(new \DateTimeImmutable());

            $fieldBuilder = $this->resolveFieldBuilderState($request, $field);
            if (null !== $fieldBuilder['error']) {
                $form->addError(new FormError($fieldBuilder['error']));
            }

            if ($dealFieldRepository->aliasExists($field->getAlias(), $field->getId())) {
                $form->get('alias')->addError(new FormError('This API alias is already in use.'));
            }
        }

        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager->persist($field);
            $entityManager->flush();

            return $this->finishForm($request, $returnUrl, self::class.'::indexAction');
        }

        return $this->delegateView([
            'viewParameters' => [
                'form' => $form->createView(),
                'pageTitle' => $pageTitle,
                'cancelUrl' => $returnUrl,
                'field' => $field,
                'formAction' => $formAction,
                'isModal' => $isModal,
                'fieldBuilder' => $fieldBuilder,
            ],
            'contentTemplate' => '@DealFlow/DealField/form.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_field_index',
                'route' => $this->generateUrl('mautic_dealflow_field_index'),
                'mauticContent' => 'dealflow',
            ],
        ]);
    }

    private function validateCsrfToken(CsrfTokenManagerInterface $csrfTokenManager, string $tokenId, string $submittedToken): void
    {
        if (!$csrfTokenManager->isTokenValid(new CsrfToken($tokenId, $submittedToken))) {
            throw $this->createAccessDeniedException('Invalid CSRF token.');
        }
    }

    private function resolveReturnUrl(Request $request, string $defaultUrl): string
    {
        $returnUrl = (string) $request->get('returnUrl', $defaultUrl);
        $parsedUrl = parse_url($returnUrl);
        $path = $parsedUrl['path'] ?? $defaultUrl;
        $query = [];

        if (isset($parsedUrl['query'])) {
            parse_str($parsedUrl['query'], $query);
        }

        unset(
            $query['_'],
            $query['ignoreAjax'],
            $query['mauticUserLastActive'],
            $query['mauticLastNotificationId'],
            $query['modal'],
            $query['contentOnly']
        );

        return $path.([] !== $query ? '?'.http_build_query($query) : '');
    }

    private function finishForm(Request $request, string $returnUrl, string $contentTemplate): Response
    {
        if ($this->isModalRequest($request)) {
            return $this->postActionRedirect([
                'returnUrl' => $returnUrl,
                'contentTemplate' => $contentTemplate,
                'viewParameters' => [],
                'passthroughVars' => [
                    'closeModal' => true,
                    'route' => $returnUrl,
                    'mauticContent' => 'dealflow',
                ],
            ]);
        }

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

    private function isModalRequest(Request $request): bool
    {
        return filter_var($request->get('modal', false), FILTER_VALIDATE_BOOLEAN);
    }

    /**
     * @return array{
     *     options: list<string>,
     *     defaultTextValue: string,
     *     defaultTextareaValue: string,
     *     defaultNumberValue: string,
     *     defaultDateValue: string,
     *     defaultBooleanValue: bool,
     *     defaultSelectValue: string,
     *     error: string|null
     * }
     */
    private function buildFieldBuilderState(DealField $field, ?string $error = null): array
    {
        $defaultValue = $field->getDefaultValue();
        $options = $field->getOptions();

        return [
            'options' => [] !== $options ? $options : [''],
            'defaultTextValue' => DealField::TYPE_TEXT === $field->getFieldType() ? (string) ($defaultValue ?? '') : '',
            'defaultTextareaValue' => DealField::TYPE_TEXTAREA === $field->getFieldType() ? (string) ($defaultValue ?? '') : '',
            'defaultNumberValue' => DealField::TYPE_NUMBER === $field->getFieldType() ? (string) ($defaultValue ?? '') : '',
            'defaultDateValue' => DealField::TYPE_DATE === $field->getFieldType() ? (string) ($defaultValue ?? '') : '',
            'defaultBooleanValue' => $field->getBooleanDefaultValue() ?? false,
            'defaultSelectValue' => DealField::TYPE_SELECT === $field->getFieldType() ? (string) ($defaultValue ?? '') : '',
            'error' => $error,
        ];
    }

    /**
     * @return array{
     *     options: list<string>,
     *     defaultTextValue: string,
     *     defaultTextareaValue: string,
     *     defaultNumberValue: string,
     *     defaultDateValue: string,
     *     defaultBooleanValue: bool,
     *     defaultSelectValue: string,
     *     error: string|null
     * }
     */
    private function resolveFieldBuilderState(Request $request, DealField $field): array
    {
        /** @var array<string, mixed> $payload */
        $payload = $request->request->all()['deal_field_builder'] ?? [];
        $options = $this->normalizeOptionValues($payload['options'] ?? []);
        $error = null;

        if ($field->supportsOptions()) {
            $field->setOptions($options);

            if ([] === $options) {
                $error = 'Add at least one option for select fields.';
            }
        } else {
            $field->setOptions([]);
        }

        $defaultValue = null;

        switch ($field->getFieldType()) {
            case DealField::TYPE_TEXT:
                $defaultValue = $this->normalizeScalarDefault($payload['default_text_value'] ?? null);
                break;
            case DealField::TYPE_TEXTAREA:
                $defaultValue = $this->normalizeTextareaDefault($payload['default_textarea_value'] ?? null);
                break;
            case DealField::TYPE_NUMBER:
                $defaultValue = $this->normalizeScalarDefault($payload['default_number_value'] ?? null);
                if (null !== $defaultValue && !is_numeric($defaultValue)) {
                    $error ??= 'Default number must be numeric.';
                    $defaultValue = null;
                }
                break;
            case DealField::TYPE_DATE:
                $defaultValue = $this->normalizeScalarDefault($payload['default_date_value'] ?? null);
                if (null !== $defaultValue && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $defaultValue)) {
                    $error ??= 'Default date must use the YYYY-MM-DD format.';
                    $defaultValue = null;
                }
                break;
            case DealField::TYPE_SELECT:
                $defaultValue = $this->normalizeScalarDefault($payload['default_select_value'] ?? null);
                if (null !== $defaultValue && !in_array($defaultValue, $field->getOptions(), true)) {
                    $error ??= 'Default option must match one of the configured select choices.';
                    $defaultValue = null;
                }
                break;
            case DealField::TYPE_BOOLEAN:
                if ($field->isRequired()) {
                    $defaultValue = $this->normalizeBooleanDefault($payload['default_boolean_value'] ?? null);
                }
                break;
        }

        if (DealField::TYPE_BOOLEAN !== $field->getFieldType() || !$field->isRequired()) {
            $field->setDefaultValue($defaultValue);
        } elseif (null === $defaultValue) {
            $field->setDefaultValue('0');
        } else {
            $field->setDefaultValue($defaultValue);
        }

        return $this->buildFieldBuilderState($field, $error);
    }

    /**
     * @return list<string>
     */
    private function normalizeOptionValues(mixed $rawOptions): array
    {
        if (!is_array($rawOptions)) {
            return [];
        }

        $options = [];

        foreach ($rawOptions as $option) {
            $value = trim((string) $option);
            if ('' === $value || in_array($value, $options, true)) {
                continue;
            }

            $options[] = $value;
        }

        return $options;
    }

    private function normalizeScalarDefault(mixed $value): ?string
    {
        $normalized = trim((string) $value);

        return '' !== $normalized ? $normalized : null;
    }

    private function normalizeTextareaDefault(mixed $value): ?string
    {
        $normalized = trim((string) $value);

        return '' !== $normalized ? $normalized : null;
    }

    private function normalizeBooleanDefault(mixed $value): string
    {
        return in_array((string) $value, ['1', 'true', 'yes', 'on'], true) ? '1' : '0';
    }
}
