<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Controller;

use Doctrine\Persistence\ManagerRegistry;
use Mautic\CoreBundle\Controller\CommonController;
use MauticPlugin\DealFlowBundle\Entity\Deal;
use MauticPlugin\DealFlowBundle\Entity\DealAttachmentRepository;
use MauticPlugin\DealFlowBundle\Entity\DealNoteRepository;
use MauticPlugin\DealFlowBundle\Entity\DealRepository;
use MauticPlugin\DealFlowBundle\Entity\DealStageRepository;
use MauticPlugin\DealFlowBundle\Entity\DealTaskRepository;
use MauticPlugin\DealFlowBundle\Model\DealModel;
use MauticPlugin\DealFlowBundle\Service\AttachmentStorage;
use MauticPlugin\DealFlowBundle\Service\AutomationOptionsProvider;
use MauticPlugin\DealFlowBundle\Service\DealAuthorization;
use MauticPlugin\DealFlowBundle\Service\DealContextBuilder;
use MauticPlugin\DealFlowBundle\Service\DealFieldManager;
use MauticPlugin\DealFlowBundle\Service\DealManager;
use MauticPlugin\DealFlowBundle\Service\DealTimelineBuilder;
use MauticPlugin\DealFlowBundle\Service\DefaultPipelineInitializer;
use MauticPlugin\DealFlowBundle\Service\HealthIndicatorCalculator;
use MauticPlugin\DealFlowBundle\Service\PipelineVelocityCalculator;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

/**
 * Thin facade preserving all original routes for backward compatibility.
 *
 * All business logic lives in focused sub-controllers:
 * - DealListController   — pipelineAction, indexAction
 * - DealFormController   — newAction, editAction
 * - DealViewController   — viewAction
 * - DealViewAjaxController — AJAX actions (notes, tasks, contacts, inline edit, etc.)
 * - DealBatchController  — batchAction, undoBatchAction
 * - DealAttachmentController — addAttachmentAction, downloadAttachmentAction, deleteAttachmentAction
 *
 * The methods inlineEditAction() and detachContactAction() are kept inline because
 * integration tests instantiate DealController directly and call these methods.
 */
final class DealController extends CommonController
{
    public function pipelineAction(
        Request $request,
        DealAuthorization $authorization,
        DefaultPipelineInitializer $pipelineInitializer,
        DealRepository $dealRepository,
        DealContextBuilder $contextBuilder,
        HealthIndicatorCalculator $healthIndicator,
        PipelineVelocityCalculator $velocityCalculator,
    ): Response {
        return $this->forward(DealListController::class.'::pipelineAction', ['_route' => 'mautic_dealflow_pipeline'], $request->query->all());
    }

    public function indexAction(
        Request $request,
        DealAuthorization $authorization,
        DefaultPipelineInitializer $pipelineInitializer,
        DealRepository $dealRepository,
        DealContextBuilder $contextBuilder,
        DealStageRepository $stageRepository,
        DealFieldManager $dealFieldManager,
        CsrfTokenManagerInterface $csrfTokenManager,
        int $page = 1,
    ): Response {
        return $this->forward(DealListController::class.'::indexAction', ['_route' => 'mautic_dealflow_index', 'page' => $page], $request->query->all());
    }

    public function newAction(
        Request $request,
        DealAuthorization $authorization,
        FormFactoryInterface $formFactory,
        ManagerRegistry $managerRegistry,
        DefaultPipelineInitializer $pipelineInitializer,
        DealModel $dealModel,
        DealFieldManager $dealFieldManager,
        AutomationOptionsProvider $automationOptionsProvider,
    ): Response {
        return $this->forward(DealFormController::class.'::newAction', ['_route' => 'mautic_dealflow_new'], $request->query->all());
    }

    public function editAction(
        int $objectId,
        Request $request,
        DealAuthorization $authorization,
        FormFactoryInterface $formFactory,
        ManagerRegistry $managerRegistry,
        DealRepository $dealRepository,
        DefaultPipelineInitializer $pipelineInitializer,
        DealModel $dealModel,
        DealFieldManager $dealFieldManager,
        DealContextBuilder $contextBuilder,
        AutomationOptionsProvider $automationOptionsProvider,
    ): Response {
        return $this->forward(DealFormController::class.'::editAction', ['_route' => 'mautic_dealflow_edit', '_route_params' => ['objectId' => $objectId], 'objectId' => $objectId], $request->query->all());
    }

    public function viewAction(
        int $objectId,
        DealAuthorization $authorization,
        ManagerRegistry $managerRegistry,
        DealRepository $dealRepository,
        DealTimelineBuilder $timelineBuilder,
        DealContextBuilder $contextBuilder,
        DealFieldManager $dealFieldManager,
        DefaultPipelineInitializer $pipelineInitializer,
    ): Response {
        return $this->forward(DealViewController::class.'::viewAction', ['_route' => 'mautic_dealflow_view', '_route_params' => ['objectId' => $objectId], 'objectId' => $objectId]);
    }

    public function moveStageAction(
        int $objectId,
        Request $request,
        DealAuthorization $authorization,
        DealRepository $dealRepository,
        DealStageRepository $stageRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        $deal = $dealRepository->find($objectId);
        if (!$deal instanceof Deal) {
            throw $this->createNotFoundException(sprintf('Deal %d not found.', $objectId));
        }

        if (!$authorization->canEditDeal($deal)) {
            return $this->accessDenied();
        }

        // CSRF is handled by Mautic's global RequestSubscriber via X-CSRF-Token header for AJAX.
        // Only validate our per-deal token for non-AJAX form submissions.
        if (!$request->isXmlHttpRequest()) {
            $this->validateCsrfToken($csrfTokenManager, sprintf('dealflow_move_stage_%d', $deal->getId()), (string) $request->request->get('_token'));
        }

        $stage = $stageRepository->find((int) $request->request->get('stage_id'));
        if (null === $stage) {
            if ($request->isXmlHttpRequest()) {
                return new JsonResponse(['success' => false, 'error' => 'Stage not found'], 400);
            }

            return $this->delegateRedirect($request->request->get('return_url', $this->generateUrl('mautic_dealflow_pipeline')));
        }

        if ($stage->getPipeline()?->getId() !== $deal->getPipeline()?->getId()) {
            if ($request->isXmlHttpRequest()) {
                return new JsonResponse(['success' => false, 'error' => 'Stage does not belong to this pipeline'], 400);
            }

            return $this->delegateRedirect($request->request->get('return_url', $this->generateUrl('mautic_dealflow_pipeline')));
        }

        $dealManager->moveToStage($deal, $stage, $this->user?->getId(), 'ui');

        if ($request->isXmlHttpRequest()) {
            return new JsonResponse(['success' => true, 'dealId' => $deal->getId(), 'stageId' => $stage->getId()]);
        }

        $returnUrl = (string) $request->request->get('return_url', $this->generateUrl('mautic_dealflow_pipeline'));

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

    public function addNoteAction(
        int $objectId,
        Request $request,
        DealAuthorization $authorization,
        DealRepository $dealRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        $deal = $dealRepository->find($objectId);
        if (!$deal instanceof Deal) {
            throw $this->createNotFoundException(sprintf('Deal %d not found.', $objectId));
        }

        if (!$authorization->canEditDeal($deal)) {
            return $this->accessDenied();
        }

        if ($request->isXmlHttpRequest()) {
            $token = $request->headers->get('X-CSRF-Token', '');
            if (!$csrfTokenManager->isTokenValid(new CsrfToken('mautic_ajax_post', $token))) {
                return new JsonResponse(['success' => false, 'error' => 'Invalid CSRF token.'], 403);
            }
        } else {
            $this->validateCsrfToken($csrfTokenManager, sprintf('dealflow_add_note_%d', $deal->getId()), (string) $request->request->get('_token'));
        }

        $body = trim((string) $request->request->get('body'));
        if ('' === $body) {
            if ($request->isXmlHttpRequest()) {
                return new JsonResponse(['success' => false, 'error' => 'Note body cannot be empty.'], 422);
            }

            $this->addFlash('error', 'Note body cannot be empty.');

            return $this->delegateRedirect($this->generateUrl('mautic_dealflow_view', ['objectId' => $objectId]));
        }

        $dealManager->addNote($deal, $body, $this->user?->getId(), 'ui');

        if ($request->isXmlHttpRequest()) {
            return new JsonResponse(['success' => true]);
        }

        return $this->delegateRedirect($this->generateUrl('mautic_dealflow_view', ['objectId' => $objectId]));
    }

    public function editNoteAction(
        int $objectId,
        int $noteId,
        Request $request,
        DealAuthorization $authorization,
        DealNoteRepository $noteRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealViewAjaxController::class.'::editNoteAction', ['_route' => 'mautic_dealflow_note_edit', '_route_params' => ['objectId' => $objectId, 'noteId' => $noteId], 'objectId' => $objectId, 'noteId' => $noteId]);
    }

    public function deleteNoteAction(
        int $objectId,
        int $noteId,
        Request $request,
        DealAuthorization $authorization,
        DealNoteRepository $noteRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealViewAjaxController::class.'::deleteNoteAction', ['_route' => 'mautic_dealflow_note_delete', '_route_params' => ['objectId' => $objectId, 'noteId' => $noteId], 'objectId' => $objectId, 'noteId' => $noteId]);
    }

    public function addTaskAction(
        int $objectId,
        Request $request,
        DealAuthorization $authorization,
        DealRepository $dealRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealViewAjaxController::class.'::addTaskAction', ['_route' => 'mautic_dealflow_task_add', '_route_params' => ['objectId' => $objectId], 'objectId' => $objectId]);
    }

    public function completeTaskAction(
        int $objectId,
        int $taskId,
        Request $request,
        DealAuthorization $authorization,
        DealTaskRepository $taskRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealViewAjaxController::class.'::completeTaskAction', ['_route' => 'mautic_dealflow_task_complete', '_route_params' => ['objectId' => $objectId, 'taskId' => $taskId], 'objectId' => $objectId, 'taskId' => $taskId]);
    }

    public function uncompleteTaskAction(
        int $objectId,
        int $taskId,
        Request $request,
        DealAuthorization $authorization,
        DealTaskRepository $taskRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealViewAjaxController::class.'::uncompleteTaskAction', ['_route' => 'mautic_dealflow_task_uncomplete', '_route_params' => ['objectId' => $objectId, 'taskId' => $taskId], 'objectId' => $objectId, 'taskId' => $taskId]);
    }

    public function editTaskAction(
        int $objectId,
        int $taskId,
        Request $request,
        DealAuthorization $authorization,
        DealTaskRepository $taskRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealViewAjaxController::class.'::editTaskAction', ['_route' => 'mautic_dealflow_task_edit', '_route_params' => ['objectId' => $objectId, 'taskId' => $taskId], 'objectId' => $objectId, 'taskId' => $taskId]);
    }

    public function deleteTaskAction(
        int $objectId,
        int $taskId,
        Request $request,
        DealAuthorization $authorization,
        DealTaskRepository $taskRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealViewAjaxController::class.'::deleteTaskAction', ['_route' => 'mautic_dealflow_task_delete', '_route_params' => ['objectId' => $objectId, 'taskId' => $taskId], 'objectId' => $objectId, 'taskId' => $taskId]);
    }

    public function associateContactAction(
        int $objectId,
        Request $request,
        DealAuthorization $authorization,
        ManagerRegistry $managerRegistry,
        DealRepository $dealRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealViewAjaxController::class.'::associateContactAction', ['_route' => 'mautic_dealflow_contact_attach', '_route_params' => ['objectId' => $objectId], 'objectId' => $objectId]);
    }

    public function detachContactAction(
        int $objectId,
        int $contactId,
        Request $request,
        DealAuthorization $authorization,
        DealRepository $dealRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        $deal = $dealRepository->find($objectId);
        if (!$deal instanceof Deal) {
            throw $this->createNotFoundException(sprintf('Deal %d not found.', $objectId));
        }

        if (!$authorization->canEditDeal($deal)) {
            throw $this->createAccessDeniedException();
        }

        // CSRF validation — accept both header and POST body token
        $token = $request->headers->get('X-CSRF-Token') ?: $request->request->get('_token');
        if (!$csrfTokenManager->isTokenValid(new CsrfToken('mautic_ajax_post', (string) $token))) {
            return new JsonResponse(['success' => false, 'error' => 'Invalid CSRF token.'], 403);
        }

        $actorId = $this->user?->getId();
        $result = $dealManager->detachContact($deal, $contactId, $actorId);

        if ($request->isXmlHttpRequest()) {
            return new JsonResponse(['success' => $result]);
        }

        return $this->redirectToRoute('mautic_dealflow_view', ['objectId' => $objectId]);
    }

    public function inlineEditAction(
        int $objectId,
        Request $request,
        DealAuthorization $authorization,
        DealRepository $dealRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        $deal = $dealRepository->find($objectId);
        if (!$deal instanceof Deal) {
            return new JsonResponse(['success' => false, 'error' => 'Deal not found.'], 404);
        }

        if (!$authorization->canEditDeal($deal)) {
            return new JsonResponse(['success' => false, 'error' => 'Access denied.'], 403);
        }

        // CSRF validation — accept both header and POST body token
        $token = $request->headers->get('X-CSRF-Token') ?: $request->request->get('_token');
        if (!$csrfTokenManager->isTokenValid(new CsrfToken('mautic_ajax_post', (string) $token))) {
            return new JsonResponse(['success' => false, 'error' => 'Invalid CSRF token.'], 403);
        }

        $field = $request->request->get('field');
        $value = $request->request->get('value');

        $allowedFields = ['confidence', 'source', 'expected_close', 'owner_id', 'company_id', 'amount', 'currency'];
        if (!in_array($field, $allowedFields, true)) {
            return new JsonResponse(['success' => false, 'error' => 'Field not editable.'], 400);
        }

        $actorId = $this->user?->getId();

        try {
            $dealManager->updateField($deal, $field, $value, $actorId);
        } catch (\Exception $e) {
            return new JsonResponse(['success' => false, 'error' => $e->getMessage()], 400);
        }

        return new JsonResponse(['success' => true, 'field' => $field, 'value' => $value]);
    }

    public function addAttachmentAction(
        int $objectId,
        Request $request,
        DealAuthorization $authorization,
        DealRepository $dealRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealAttachmentController::class.'::addAttachmentAction', ['_route' => 'mautic_dealflow_attachment_add', '_route_params' => ['objectId' => $objectId], 'objectId' => $objectId]);
    }

    public function downloadAttachmentAction(
        int $attachmentId,
        DealAuthorization $authorization,
        DealAttachmentRepository $attachmentRepository,
        AttachmentStorage $attachmentStorage,
    ): BinaryFileResponse {
        /** @var BinaryFileResponse $response */
        $response = $this->forward(DealAttachmentController::class.'::downloadAttachmentAction', ['_route' => 'mautic_dealflow_attachment_download', '_route_params' => ['attachmentId' => $attachmentId], 'attachmentId' => $attachmentId]);

        return $response;
    }

    public function deleteAttachmentAction(
        int $objectId,
        int $attachmentId,
        Request $request,
        DealAuthorization $authorization,
        DealRepository $dealRepository,
        DealAttachmentRepository $attachmentRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealAttachmentController::class.'::deleteAttachmentAction', ['_route' => 'mautic_dealflow_attachment_delete', '_route_params' => ['objectId' => $objectId, 'attachmentId' => $attachmentId], 'objectId' => $objectId, 'attachmentId' => $attachmentId]);
    }

    public function batchAction(
        Request $request,
        DealAuthorization $authorization,
        DealRepository $dealRepository,
        DealStageRepository $stageRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealBatchController::class.'::batchAction', ['_route' => 'mautic_dealflow_batch']);
    }

    public function undoBatchAction(
        Request $request,
        DealAuthorization $authorization,
        DealRepository $dealRepository,
        DealManager $dealManager,
        CsrfTokenManagerInterface $csrfTokenManager,
    ): Response {
        return $this->forward(DealBatchController::class.'::undoBatchAction', ['_route' => 'mautic_dealflow_undo']);
    }

    public function companySearchAction(
        Request $request,
        ManagerRegistry $managerRegistry,
    ): JsonResponse {
        /** @var JsonResponse $response */
        $response = $this->forward(DealViewAjaxController::class.'::companySearchAction', ['_route' => 'mautic_dealflow_company_search'], $request->query->all());

        return $response;
    }

    public function csrfTokenAction(
        CsrfTokenManagerInterface $csrfTokenManager,
    ): JsonResponse {
        $token = $csrfTokenManager->getToken('mautic_ajax_post')->getValue();

        return new JsonResponse(['token' => $token]);
    }

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