<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Controller;

use Doctrine\Persistence\ManagerRegistry;
use Mautic\CoreBundle\Controller\CommonController;
use Mautic\UserBundle\Entity\User;
use MauticPlugin\DealFlowBundle\Controller\Trait\QueryParamTrait;
use MauticPlugin\DealFlowBundle\Entity\Deal;
use MauticPlugin\DealFlowBundle\Entity\DealRepository;
use MauticPlugin\DealFlowBundle\Entity\DealTask;
use MauticPlugin\DealFlowBundle\Entity\DealTaskRepository;
use MauticPlugin\DealFlowBundle\Form\Type\DealTaskType;
use MauticPlugin\DealFlowBundle\Service\DealAuthorization;
use MauticPlugin\DealFlowBundle\Service\DealContextBuilder;
use MauticPlugin\DealFlowBundle\Service\DealManager;
use MauticPlugin\DealFlowBundle\Service\DefaultPipelineInitializer;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

final class TaskController extends CommonController
{
    use QueryParamTrait;

    public function queueAction(
        Request $request,
        DealAuthorization $authorization,
        DefaultPipelineInitializer $pipelineInitializer,
        DealTaskRepository $taskRepository,
        DealContextBuilder $contextBuilder,
        ManagerRegistry $managerRegistry,
        DealRepository $dealRepository,
    ): Response {
        if (!$authorization->canViewDeals()) {
            return $this->accessDenied();
        }

        $pipeline = $pipelineInitializer->resolvePipeline($this->queryNullableInt($request, 'pipelineId'));
        $focusOnly = $this->queryFlag($request, 'focus');
        $currentUserId = $authorization->getCurrentUserId();
        $mineOnly = $this->queryFlag($request, 'mine');
        $ownerFilterId = $mineOnly && null !== $currentUserId ? $currentUserId : $this->queryNullableInt($request, 'ownerId');
        $now = new \DateTimeImmutable();
        $page = $this->queryNullableInt($request, 'page') ?? 1;
        $pageSize = 30;

        $totalCount = $taskRepository->countQueue(
            $pipeline,
            $ownerFilterId,
            $focusOnly,
            $now,
            $currentUserId,
            $authorization->canViewOtherDeals()
        );
        $totalPages = max(1, (int) ceil($totalCount / $pageSize));
        $page = max(1, min($page, $totalPages));

        $tasks = $taskRepository->findQueuePaginated(
            $pipeline,
            $ownerFilterId,
            $focusOnly,
            $now,
            $currentUserId,
            $authorization->canViewOtherDeals(),
            $page,
            $pageSize,
        );

        $deals = array_values(array_filter(array_map(static fn (DealTask $task): ?Deal => $task->getDeal(), $tasks)));
        $dealContexts = $contextBuilder->buildForDeals($deals);
        $taskOwners = $this->buildOwnerLabels($managerRegistry);
        $metrics = $this->buildQueueMetrics($tasks);
        $editableDeals = $this->resolveEditableDeals($dealRepository, $authorization, $pipeline);

        return $this->delegateView([
            'viewParameters' => [
                'pipelines' => $pipelineInitializer->getActivePipelines(),
                'selectedPipeline' => $pipeline,
                'items' => $tasks,
                'dealContexts' => $dealContexts,
                'taskOwners' => $taskOwners,
                'metrics' => $metrics,
                'focusOnly' => $focusOnly,
                'mineOnly' => $mineOnly,
                'ownerFilterId' => $ownerFilterId,
                'currentUserId' => $currentUserId,
                'ownerChoices' => $this->buildOwnerChoices($taskOwners),
                'canCreateTask' => [] !== $editableDeals,
                'page' => $page,
                'totalPages' => $totalPages,
                'totalCount' => $totalCount,
            ],
            'contentTemplate' => '@DealFlow/Task/queue.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_task_queue',
                'route' => $this->generateUrl('mautic_dealflow_task_queue', ['pipelineId' => $pipeline->getId()]),
                'mauticContent' => 'dealflow',
            ],
        ]);
    }

    public function newAction(
        Request $request,
        DealAuthorization $authorization,
        DefaultPipelineInitializer $pipelineInitializer,
        DealRepository $dealRepository,
        DealManager $dealManager,
        ManagerRegistry $managerRegistry,
        FormFactoryInterface $formFactory,
    ): Response {
        if (!$authorization->canViewDeals()) {
            return $this->accessDenied();
        }

        $pipeline = $pipelineInitializer->resolvePipeline($this->queryNullableInt($request, 'pipelineId'));
        $returnUrl = $this->resolveReturnUrl($request, $pipeline->getId());
        $editableDeals = $this->resolveEditableDeals($dealRepository, $authorization, $pipeline);
        $taskOwnerChoices = $this->buildOwnerChoices($this->buildOwnerLabels($managerRegistry));
        $selectedDealId = $this->queryNullableInt($request, 'dealId');

        if (null !== $selectedDealId && !isset($editableDeals[$selectedDealId])) {
            $selectedDealId = null;
        }

        $form = $formFactory->create(DealTaskType::class, [
            'dealId' => $selectedDealId,
            'priority' => DealTask::PRIORITY_NORMAL,
            'ownerId' => null,
            'title' => '',
            'description' => '',
            'dueAt' => null,
            'reminderAt' => null,
        ], [
            'deal_choices' => $this->buildDealChoices($editableDeals),
            'owner_choices' => $taskOwnerChoices,
        ]);
        $formAction = $this->generateUrl('mautic_dealflow_task_new', array_filter([
            'pipelineId' => $pipeline->getId(),
            'dealId' => $selectedDealId,
            'returnUrl' => $returnUrl,
            'modal' => $this->isModalRequest($request) ? 1 : null,
            'contentOnly' => $this->isModalRequest($request) ? 1 : null,
        ], static fn (mixed $value): bool => null !== $value && '' !== $value));
        $form->handleRequest($request);

        if ($form->isSubmitted()) {
            /** @var array<string, mixed> $prevalidationData */
            $prevalidationData = $form->getData();
            if ('' === trim((string) ($prevalidationData['title'] ?? ''))) {
                $form->get('title')->addError(new FormError('Task title is required.'));
            }
        }

        if ($form->isSubmitted() && $form->isValid()) {
            /** @var array<string, mixed> $data */
            $data = $form->getData();
            $dealId = isset($data['dealId']) ? (int) $data['dealId'] : 0;
            $deal = $editableDeals[$dealId] ?? null;

            if (!$deal instanceof Deal) {
                $form->get('dealId')->addError(new FormError('Select a deal.'));
            } else {
                $dealManager->addTask(
                    $deal,
                    [
                        'title' => $data['title'] ?? '',
                        'description' => $data['description'] ?? null,
                        'priority' => $data['priority'] ?? DealTask::PRIORITY_NORMAL,
                        'due_at' => $data['dueAt'] ?? null,
                        'reminder_at' => $data['reminderAt'] ?? null,
                        'owner_id' => $data['ownerId'] ?? null,
                    ],
                    $this->user?->getId(),
                    'ui'
                );

                return $this->finishTaskForm($request, $returnUrl);
            }
        }

        return $this->delegateView([
            'viewParameters' => [
                'form' => $form->createView(),
                'formAction' => $formAction,
                'pageTitle' => 'New Task',
                'cancelUrl' => $returnUrl,
                'isModal' => $this->isModalRequest($request),
                'selectedPipeline' => $pipeline,
                'canCreateTask' => [] !== $editableDeals,
            ],
            'contentTemplate' => '@DealFlow/Task/form.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_task_queue',
                'route' => $this->generateUrl('mautic_dealflow_task_queue', ['pipelineId' => $pipeline->getId()]),
                'mauticContent' => 'dealflow',
            ],
        ]);
    }

    /**
     * @param list<DealTask> $tasks
     *
     * @return array{open: int, urgent: int, overdue: int, dueToday: int, remindersDue: int}
     */
    private function buildQueueMetrics(array $tasks): array
    {
        $today = new \DateTimeImmutable('today');
        $tomorrow = $today->modify('+1 day');
        $now = new \DateTimeImmutable();
        $metrics = [
            'open' => count($tasks),
            'urgent' => 0,
            'overdue' => 0,
            'dueToday' => 0,
            'remindersDue' => 0,
        ];

        foreach ($tasks as $task) {
            if (in_array($task->getPriority(), [DealTask::PRIORITY_HIGH, DealTask::PRIORITY_URGENT], true)) {
                ++$metrics['urgent'];
            }

            $dueAt = $task->getDueAt();
            if ($dueAt instanceof \DateTimeInterface) {
                if ($dueAt < $today) {
                    ++$metrics['overdue'];
                } elseif ($dueAt >= $today && $dueAt < $tomorrow) {
                    ++$metrics['dueToday'];
                }
            }

            $reminderAt = $task->getReminderAt();
            if ($reminderAt instanceof \DateTimeInterface && $reminderAt <= $now) {
                ++$metrics['remindersDue'];
            }
        }

        return $metrics;
    }

    /**
     * @return array<int, Deal>
     */
    private function resolveEditableDeals(
        DealRepository $dealRepository,
        DealAuthorization $authorization,
        \MauticPlugin\DealFlowBundle\Entity\Pipeline $pipeline,
    ): array {
        $editableDeals = [];

        foreach ($dealRepository->findForPipeline(
            $pipeline,
            ['status' => 'open'],
            $authorization->getCurrentUserId(),
            $authorization->canViewOtherDeals()
        ) as $deal) {
            if (!$authorization->canEditDeal($deal) || null === $deal->getId()) {
                continue;
            }

            $editableDeals[$deal->getId()] = $deal;
        }

        return $editableDeals;
    }

    /**
     * @return array<int, string>
     */
    private function buildOwnerLabels(ManagerRegistry $managerRegistry): array
    {
        $labels = [];

        foreach ($managerRegistry->getRepository(User::class)->findAll() as $owner) {
            $ownerId = $owner->getId();
            if (null === $ownerId) {
                continue;
            }

            $labels[$ownerId] = method_exists($owner, 'getName') && '' !== trim((string) $owner->getName())
                ? trim((string) $owner->getName())
                : (method_exists($owner, 'getUserIdentifier') ? (string) $owner->getUserIdentifier() : sprintf('User #%d', $ownerId));
        }

        return $labels;
    }

    /**
     * @param array<int, Deal> $editableDeals
     *
     * @return array<string, int>
     */
    private function buildDealChoices(array $editableDeals): array
    {
        $choices = [];

        foreach ($editableDeals as $deal) {
            $label = $deal->getTitle();
            if ($deal->getStage()?->getName()) {
                $label .= sprintf(' · %s', $deal->getStage()->getName());
            }

            $choices[$label] = (int) $deal->getId();
        }

        return $choices;
    }

    /**
     * @param array<int, string> $ownerLabels
     *
     * @return array<string, int>
     */
    private function buildOwnerChoices(array $ownerLabels): array
    {
        $choices = [];

        foreach ($ownerLabels as $ownerId => $label) {
            $choices[sprintf('%s (#%d)', $label, $ownerId)] = $ownerId;
        }

        ksort($choices, SORT_NATURAL | SORT_FLAG_CASE);

        return $choices;
    }

    private function resolveReturnUrl(Request $request, int $pipelineId): string
    {
        $returnUrl = (string) $request->get('returnUrl', $this->generateUrl('mautic_dealflow_task_queue', ['pipelineId' => $pipelineId]));
        $parsedUrl = parse_url($returnUrl);
        $path = $parsedUrl['path'] ?? $this->generateUrl('mautic_dealflow_task_queue', ['pipelineId' => $pipelineId]);
        $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 finishTaskForm(Request $request, string $returnUrl): Response
    {
        if ($this->isModalRequest($request)) {
            return $this->postActionRedirect([
                'returnUrl' => $returnUrl,
                'contentTemplate' => self::class.'::queueAction',
                '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);
    }
}
