<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Controller;

use Doctrine\ORM\EntityManagerInterface;
use Mautic\CoreBundle\Controller\CommonController;
use MauticPlugin\DealFlowBundle\Entity\DealRepository;
use MauticPlugin\DealFlowBundle\Entity\Pipeline;
use MauticPlugin\DealFlowBundle\Entity\PipelineRepository;
use MauticPlugin\DealFlowBundle\Form\Type\PipelineType;
use MauticPlugin\DealFlowBundle\Service\DealAuthorization;
use MauticPlugin\DealFlowBundle\Service\DefaultPipelineInitializer;
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 PipelineController extends CommonController
{
    public function indexAction(
        DealAuthorization $authorization,
        PipelineRepository $pipelineRepository,
        DealRepository $dealRepository,
        DefaultPipelineInitializer $pipelineInitializer,
    ): Response {
        if (!$authorization->canViewSettings()) {
            return $this->accessDenied();
        }

        $pipelineInitializer->ensureDefaultPipeline();
        $pipelines = $pipelineRepository->findBy([], ['isDefault' => 'DESC', 'name' => 'ASC', 'id' => 'ASC']);
        $dealCounts = [];
        $stageCounts = [];

        foreach ($pipelines as $pipeline) {
            $pipelineInitializer->ensureStages($pipeline);
            $dealCounts[$pipeline->getId() ?? spl_object_id($pipeline)] = $dealRepository->countByPipeline($pipeline);
            $stageCounts[$pipeline->getId() ?? spl_object_id($pipeline)] = count($pipelineInitializer->getOrderedStages($pipeline));
        }

        return $this->delegateView([
            'viewParameters' => [
                'items' => $pipelines,
                'dealCounts' => $dealCounts,
                'stageCounts' => $stageCounts,
                'canEditSettings' => $authorization->canEditSettings(),
            ],
            'contentTemplate' => '@DealFlow/Pipeline/list.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_pipeline_settings_index',
                'route' => $this->generateUrl('mautic_dealflow_pipeline_settings_index'),
                'mauticContent' => 'dealflow',
            ],
        ]);
    }

    public function newAction(
        Request $request,
        DealAuthorization $authorization,
        FormFactoryInterface $formFactory,
        EntityManagerInterface $entityManager,
        PipelineRepository $pipelineRepository,
        DefaultPipelineInitializer $pipelineInitializer,
    ): Response {
        if (!$authorization->canEditSettings()) {
            return $this->accessDenied();
        }

        $isModal = $this->isModalRequest($request);
        $returnUrl = $this->resolveReturnUrl($request, $this->generateUrl('mautic_dealflow_pipeline_settings_index'));
        $pipeline = new Pipeline();
        $pipeline->setIsPublished(true);
        $formAction = $this->generateUrl('mautic_dealflow_pipeline_settings_new', array_filter([
            'returnUrl' => $returnUrl,
            'modal' => $isModal ? 1 : null,
            'contentOnly' => $isModal ? 1 : null,
        ], static fn (mixed $value): bool => null !== $value && '' !== $value));
        $form = $formFactory->create(PipelineType::class, $pipeline);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $pipeline->setDateModified(new \DateTime());
            $entityManager->persist($pipeline);
            $entityManager->flush();

            $this->normalizeDefaultPipeline($pipeline, $pipelineRepository, $entityManager);
            $pipelineInitializer->ensureStages($pipeline);

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

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

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

        $isModal = $this->isModalRequest($request);
        $pipeline = $pipelineRepository->find($objectId);
        if (!$pipeline instanceof Pipeline) {
            throw $this->createNotFoundException(sprintf('Pipeline %d not found.', $objectId));
        }

        $returnUrl = $this->resolveReturnUrl($request, $this->generateUrl('mautic_dealflow_pipeline_settings_index'));
        $formAction = $this->generateUrl('mautic_dealflow_pipeline_settings_edit', array_filter([
            'objectId' => $pipeline->getId(),
            'returnUrl' => $returnUrl,
            'modal' => $isModal ? 1 : null,
            'contentOnly' => $isModal ? 1 : null,
        ], static fn (mixed $value): bool => null !== $value && '' !== $value));
        $form = $formFactory->create(PipelineType::class, $pipeline);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $pipeline->setDateModified(new \DateTime());
            $entityManager->flush();
            $this->normalizeDefaultPipeline($pipeline, $pipelineRepository, $entityManager);

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

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

    public function deleteAction(
        int $objectId,
        Request $request,
        DealAuthorization $authorization,
        EntityManagerInterface $entityManager,
        PipelineRepository $pipelineRepository,
        DealRepository $dealRepository,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        if (!$authorization->canEditSettings()) {
            return $this->accessDenied();
        }

        $pipeline = $pipelineRepository->find($objectId);
        if (!$pipeline instanceof Pipeline) {
            throw $this->createNotFoundException(sprintf('Pipeline %d not found.', $objectId));
        }

        $pipelineCount = $pipelineRepository->countAll();
        $dealCount = $dealRepository->countByPipeline($pipeline);
        $canDelete = $pipelineCount > 1 && 0 === $dealCount;
        $errorMessage = null;

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

            if (!$canDelete) {
                if ($pipelineCount <= 1) {
                    $errorMessage = 'You cannot delete the last remaining pipeline.';
                } else {
                    $errorMessage = 'Move or close all deals in this pipeline before deleting it.';
                }
            } else {
                $wasDefault = $pipeline->isDefault();
                $entityManager->remove($pipeline);
                $entityManager->flush();

                if ($wasDefault || !($pipelineRepository->findMarkedDefaultPipeline() instanceof Pipeline)) {
                    $fallback = $pipelineRepository->findFirstActivePipeline();
                    if ($fallback instanceof Pipeline) {
                        $fallback->setIsDefault(true)
                            ->setIsPublished(true)
                            ->setDateModified(new \DateTime());
                        $entityManager->flush();
                    }
                }

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

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

        return $this->delegateView([
            'viewParameters' => [
                'pipeline' => $pipeline,
                'dealCount' => $dealCount,
                'cancelUrl' => $this->generateUrl('mautic_dealflow_pipeline_settings_index'),
                'canDelete' => $canDelete,
                'errorMessage' => $errorMessage,
                'pipelineCount' => $pipelineCount,
            ],
            'contentTemplate' => '@DealFlow/Pipeline/delete.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_pipeline_settings_index',
                'route' => $this->generateUrl('mautic_dealflow_pipeline_settings_delete', ['objectId' => $pipeline->getId()]),
                'mauticContent' => 'dealflow',
            ],
        ]);
    }

    private function normalizeDefaultPipeline(
        Pipeline $pipeline,
        PipelineRepository $pipelineRepository,
        EntityManagerInterface $entityManager,
    ): void {
        if ($pipeline->isDefault()) {
            $pipeline->setIsPublished(true)
                ->setDateModified(new \DateTime());
            $entityManager->flush();
            $pipelineRepository->clearDefaultFlag($pipeline->getId());

            return;
        }

        if (!$pipelineRepository->findMarkedDefaultPipeline() instanceof Pipeline) {
            $pipeline->setIsDefault(true)
                ->setIsPublished(true)
                ->setDateModified(new \DateTime());
            $entityManager->flush();
        }
    }

    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);
    }
}
