<?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\DealStage;
use MauticPlugin\DealFlowBundle\Entity\DealStageRepository;
use MauticPlugin\DealFlowBundle\Form\Type\DealStageType;
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 StageController extends CommonController
{
    public function indexAction(
        Request $request,
        DealAuthorization $authorization,
        DefaultPipelineInitializer $pipelineInitializer,
    ): Response {
        if (!$authorization->canViewSettings()) {
            return $this->accessDenied();
        }

        $pipelineId = $request->query->getInt('pipelineId', 0) ?: null;
        $pipeline = $pipelineInitializer->resolvePipeline($pipelineId);

        return $this->delegateView([
            'viewParameters' => [
                'pipelines' => $pipelineInitializer->getActivePipelines(),
                'selectedPipeline' => $pipeline,
                'items' => $pipelineInitializer->getOrderedStages($pipeline),
                'canEditSettings' => $authorization->canEditSettings(),
            ],
            'contentTemplate' => '@DealFlow/Stage/list.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_stage_index',
                'route' => $this->generateUrl('mautic_dealflow_stage_index', ['pipelineId' => $pipeline->getId()]),
                'mauticContent' => 'dealflow',
            ],
        ]);
    }

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

        $isModal = $this->isModalRequest($request);
        $stage = new DealStage();
        $pipelineId = $request->query->getInt('pipelineId', 0) ?: null;
        $pipeline = $pipelineInitializer->resolvePipeline($pipelineId);
        $returnUrl = $this->resolveReturnUrl($request, $this->generateUrl('mautic_dealflow_stage_index', ['pipelineId' => $pipeline->getId()]));
        $stage->setPipeline($pipeline)
            ->setSortOrder($stageRepository->getNextSortOrder($pipeline))
            ->setIsPublished(true);

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

        if ($form->isSubmitted() && $form->isValid()) {
            if ('' === $stage->getSlug()) {
                $stage->setSlug($pipelineInitializer->slugify($stage->getName()));
            }

            $stage->setDateModified(new \DateTime());
            $entityManager->persist($stage);
            $entityManager->flush();

            return $this->finishForm($request, $returnUrl, self::class.'::indexAction', ['pipelineId' => $pipeline->getId()]);
        }

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

    public function moveAction(
        int $objectId,
        string $direction,
        Request $request,
        DealAuthorization $authorization,
        EntityManagerInterface $entityManager,
        DealStageRepository $stageRepository,
        DefaultPipelineInitializer $pipelineInitializer,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        if (!$authorization->canEditSettings()) {
            return $this->accessDenied();
        }

        $stage = $stageRepository->find($objectId);
        if (!$stage instanceof DealStage) {
            throw $this->createNotFoundException(sprintf('Stage %d not found.', $objectId));
        }

        $this->validateCsrfToken(
            $csrfTokenManager,
            sprintf('dealflow_stage_move_%d_%s', $stage->getId(), $direction),
            (string) $request->request->get('_token')
        );

        if (!in_array($direction, ['up', 'down'], true)) {
            throw $this->createNotFoundException(sprintf('Unsupported stage move direction "%s".', $direction));
        }

        $pipeline = $stage->getPipeline() ?? $pipelineInitializer->ensureDefaultPipeline();
        $stages = $pipelineInitializer->getOrderedStages($pipeline);
        $index = null;

        foreach ($stages as $position => $candidate) {
            if ($candidate->getId() === $stage->getId()) {
                $index = $position;
                break;
            }
        }

        if (null === $index) {
            throw $this->createNotFoundException(sprintf('Stage %d not found in pipeline ordering.', $objectId));
        }

        $targetIndex = 'up' === $direction ? $index - 1 : $index + 1;
        if (!isset($stages[$targetIndex])) {
            return $this->delegateRedirect($this->generateUrl('mautic_dealflow_stage_index', ['pipelineId' => $pipeline->getId()]));
        }

        [$stages[$index], $stages[$targetIndex]] = [$stages[$targetIndex], $stages[$index]];

        foreach (array_values($stages) as $position => $orderedStage) {
            $orderedStage->setSortOrder(($position + 1) * 10);
            $orderedStage->setDateModified(new \DateTime());
        }

        $entityManager->flush();

        return $this->delegateRedirect($this->generateUrl('mautic_dealflow_stage_index', ['pipelineId' => $pipeline->getId()]));
    }

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

        $isModal = $this->isModalRequest($request);
        $stage = $entityManager->getRepository(DealStage::class)->find($objectId);
        if (!$stage instanceof DealStage) {
            throw $this->createNotFoundException(sprintf('Stage %d not found.', $objectId));
        }

        $pipeline = $stage->getPipeline() ?? $pipelineInitializer->ensureDefaultPipeline();
        $returnUrl = $this->resolveReturnUrl($request, $this->generateUrl('mautic_dealflow_stage_index', ['pipelineId' => $pipeline->getId()]));
        $formAction = $this->generateUrl('mautic_dealflow_stage_edit', array_filter([
            'objectId' => $stage->getId(),
            'pipelineId' => $pipeline->getId(),
            'returnUrl' => $returnUrl,
            'modal' => $isModal ? 1 : null,
            'contentOnly' => $isModal ? 1 : null,
        ], static fn (mixed $value): bool => null !== $value && '' !== $value));

        $form = $formFactory->create(DealStageType::class, $stage);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            if ('' === $stage->getSlug()) {
                $stage->setSlug($pipelineInitializer->slugify($stage->getName()));
            }

            $stage->setDateModified(new \DateTime());
            $entityManager->flush();

            return $this->finishForm($request, $returnUrl, self::class.'::indexAction', ['pipelineId' => $pipeline->getId()]);
        }

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

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

        $stage = $stageRepository->find($objectId);
        if (!$stage instanceof DealStage) {
            throw $this->createNotFoundException(sprintf('Stage %d not found.', $objectId));
        }

        $pipeline = $stage->getPipeline() ?? $pipelineInitializer->ensureDefaultPipeline();
        $allStages = $pipelineInitializer->getOrderedStages($pipeline);
        $replacementStages = array_values(array_filter(
            $pipelineInitializer->getOrderedStages($pipeline, true),
            static fn (DealStage $candidate): bool => $candidate->getId() !== $stage->getId()
        ));
        $dealCount = $dealRepository->countByStage($stage);
        $errorMessage = null;
        $canDelete = count($allStages) > 1;

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

            if (!$canDelete) {
                $errorMessage = 'You cannot delete the last remaining stage in the pipeline.';
            } elseif ($dealCount > 0) {
                $replacementStageId = (int) $request->request->get('replacement_stage_id');
                $replacementStage = null;

                foreach ($replacementStages as $candidate) {
                    if ($candidate->getId() === $replacementStageId) {
                        $replacementStage = $candidate;
                        break;
                    }
                }

                if (!$replacementStage instanceof DealStage) {
                    $errorMessage = 'Select a destination stage for the existing deals before deleting this stage.';
                } else {
                    $dealRepository->moveDealsToStage($stage, $replacementStage, $this->user?->getId());
                }
            }

            if (null === $errorMessage) {
                $entityManager->remove($stage);
                $entityManager->flush();
                $this->normalizeStageOrder($pipelineInitializer->getOrderedStages($pipeline), $entityManager);
                $this->addFlash('notice', 'Stage deleted.');

                return $this->delegateRedirect($this->generateUrl('mautic_dealflow_stage_index', ['pipelineId' => $pipeline->getId()]));
            }
        }

        return $this->delegateView([
            'viewParameters' => [
                'stage' => $stage,
                'dealCount' => $dealCount,
                'replacementStages' => $replacementStages,
                'cancelUrl' => $this->generateUrl('mautic_dealflow_stage_index', ['pipelineId' => $pipeline->getId()]),
                'errorMessage' => $errorMessage,
                'canDelete' => $canDelete,
                'selectedPipeline' => $pipeline,
            ],
            'contentTemplate' => '@DealFlow/Stage/delete.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_stage_index',
                'route' => $this->generateUrl('mautic_dealflow_stage_delete', ['objectId' => $stage->getId(), 'pipelineId' => $pipeline->getId()]),
                '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) : '');
    }

    /**
     * @param array<string, mixed> $viewParameters
     */
    private function finishForm(Request $request, string $returnUrl, string $contentTemplate, array $viewParameters = []): Response
    {
        if ($this->isModalRequest($request)) {
            return $this->postActionRedirect([
                'returnUrl' => $returnUrl,
                'contentTemplate' => $contentTemplate,
                'viewParameters' => $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);
    }

    /**
     * @param list<DealStage> $stages
     */
    private function normalizeStageOrder(array $stages, EntityManagerInterface $entityManager): void
    {
        foreach (array_values($stages) as $position => $stage) {
            $stage->setSortOrder(($position + 1) * 10);
            $stage->setDateModified(new \DateTime());
        }

        $entityManager->flush();
    }
}
