<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Controller;

use Doctrine\ORM\EntityManagerInterface;
use Mautic\CoreBundle\Controller\CommonController;
use MauticPlugin\DealFlowBundle\Entity\InboundWebhookSource;
use MauticPlugin\DealFlowBundle\Entity\InboundWebhookSourceRepository;
use MauticPlugin\DealFlowBundle\Form\Type\InboundWebhookSourceType;
use MauticPlugin\DealFlowBundle\Service\DealAuthorization;
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 InboundWebhookSourceController extends CommonController
{
    public function indexAction(
        DealAuthorization $authorization,
        InboundWebhookSourceRepository $sourceRepository,
    ): Response {
        if (!$authorization->canViewSettings()) {
            return $this->accessDenied();
        }

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

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

        $source = (new InboundWebhookSource())
            ->setSourceKey($this->generateSourceKey($sourceRepository))
            ->setSecret($this->generateSecret())
            ->setIsActive(true);

        $form = $formFactory->create(InboundWebhookSourceType::class, $source);
        $form->handleRequest($request);

        if ($form->isSubmitted()) {
            $this->normalizeAndValidate($source, $sourceRepository, $form);
        }

        if ($form->isSubmitted() && $form->isValid()) {
            $source->setUpdatedAt(new \DateTimeImmutable());
            $entityManager->persist($source);
            $entityManager->flush();

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

        return $this->delegateView([
            'viewParameters' => [
                'form' => $form->createView(),
                'pageTitle' => 'New inbound webhook source',
                'cancelUrl' => $this->generateUrl('mautic_dealflow_inbound_webhook_index'),
                'endpointPath' => $this->getEndpointPath($source->getSourceKey()),
            ],
            'contentTemplate' => '@DealFlow/InboundWebhookSource/form.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_inbound_webhook_index',
                'route' => $this->generateUrl('mautic_dealflow_inbound_webhook_new'),
                'mauticContent' => 'dealflow',
            ],
        ]);
    }

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

        $source = $sourceRepository->find($objectId);
        if (!$source instanceof InboundWebhookSource) {
            throw $this->createNotFoundException(sprintf('Inbound webhook source %d not found.', $objectId));
        }

        $form = $formFactory->create(InboundWebhookSourceType::class, $source);
        $form->handleRequest($request);

        if ($form->isSubmitted()) {
            $this->normalizeAndValidate($source, $sourceRepository, $form);
        }

        if ($form->isSubmitted() && $form->isValid()) {
            $source->setUpdatedAt(new \DateTimeImmutable());
            $entityManager->flush();

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

        return $this->delegateView([
            'viewParameters' => [
                'form' => $form->createView(),
                'pageTitle' => 'Edit inbound webhook source',
                'cancelUrl' => $this->generateUrl('mautic_dealflow_inbound_webhook_index'),
                'source' => $source,
                'endpointPath' => $this->getEndpointPath($source->getSourceKey()),
            ],
            'contentTemplate' => '@DealFlow/InboundWebhookSource/form.html.twig',
            'passthroughVars' => [
                'activeLink' => '#mautic_dealflow_inbound_webhook_index',
                'route' => $this->generateUrl('mautic_dealflow_inbound_webhook_edit', ['objectId' => $source->getId()]),
                'mauticContent' => 'dealflow',
            ],
        ]);
    }

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

        $source = $sourceRepository->find($objectId);
        if (!$source instanceof InboundWebhookSource) {
            throw $this->createNotFoundException(sprintf('Inbound webhook source %d not found.', $objectId));
        }

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

            $entityManager->remove($source);
            $entityManager->flush();

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

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

    private function normalizeAndValidate(
        InboundWebhookSource $source,
        InboundWebhookSourceRepository $sourceRepository,
        \Symfony\Component\Form\FormInterface $form,
    ): void {
        $normalizedSourceKey = $this->normalizeSourceKey($source->getSourceKey());
        if ('' === $normalizedSourceKey) {
            $form->get('sourceKey')->addError(new FormError('Enter a valid endpoint key using letters, numbers, hyphens, or underscores.'));
        } else {
            $source->setSourceKey($normalizedSourceKey);
        }

        $secret = trim($source->getSecret());
        if ('' === $secret) {
            $form->get('secret')->addError(new FormError('Signing secret is required.'));
        } else {
            $source->setSecret($secret);
        }

        $existing = $sourceRepository->findOneBy(['sourceKey' => $source->getSourceKey()]);
        if ($existing instanceof InboundWebhookSource && $existing->getId() !== $source->getId()) {
            $form->get('sourceKey')->addError(new FormError('Endpoint key must be unique.'));
        }
    }

    private function generateSourceKey(InboundWebhookSourceRepository $sourceRepository): string
    {
        do {
            $candidate = 'src_'.bin2hex(random_bytes(6));
        } while ($sourceRepository->findOneBy(['sourceKey' => $candidate]) instanceof InboundWebhookSource);

        return $candidate;
    }

    private function generateSecret(): string
    {
        return bin2hex(random_bytes(32));
    }

    private function getEndpointPath(string $sourceKey): string
    {
        if ('' === $sourceKey) {
            return '/dealflow/webhooks/inbound/{sourceKey}';
        }

        return $this->generateUrl('mautic_public_dealflow_webhook_source', ['sourceKey' => $sourceKey]);
    }

    private function normalizeSourceKey(string $sourceKey): string
    {
        $normalized = strtolower(trim($sourceKey));
        $normalized = preg_replace('/[^a-z0-9_-]+/', '-', $normalized) ?? '';

        return trim($normalized, '-_');
    }

    private function validateCsrfToken(
        CsrfTokenManagerInterface $csrfTokenManager,
        string $tokenId,
        string $submittedToken,
    ): void {
        if ($csrfTokenManager->isTokenValid(new CsrfToken($tokenId, $submittedToken))) {
            return;
        }

        throw $this->createAccessDeniedException('Invalid CSRF token.');
    }
}
