<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Monitoring\Error;

use CpCreation\VitiCore\Monitoring\Error\Message\DeliverErrorReportMessage;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Messenger\MessageBusInterface;

final readonly class HttpErrorSubscriber implements EventSubscriberInterface
{
    private const string DISPATCHED_ATTRIBUTE = '_viticore_error_report_dispatched';
    private const string THROWABLE_ATTRIBUTE = '_viticore_error_report_throwable';

    public function __construct(
        private ErrorMonitoringConfiguration $configuration,
        private ErrorReportFactory $reportFactory,
        private MessageBusInterface $messageBus,
        private ?LoggerInterface $logger = null,
    ) {}

    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::EXCEPTION => ['onKernelException', 256],
            KernelEvents::RESPONSE => ['onKernelResponse', -256],
        ];
    }

    public function onKernelException(ExceptionEvent $event): void
    {
        if (HttpKernelInterface::MAIN_REQUEST !== $event->getRequestType()) {
            return;
        }

        $event->getRequest()->attributes->set(self::THROWABLE_ATTRIBUTE, $event->getThrowable());
    }

    public function onKernelResponse(ResponseEvent $event): void
    {
        if (HttpKernelInterface::MAIN_REQUEST !== $event->getRequestType()) {
            return;
        }

        $request = $event->getRequest();
        $statusCode = $event->getResponse()->getStatusCode();
        if ($statusCode < 500 || $statusCode > 599) {
            return;
        }

        $throwable = $request->attributes->get(self::THROWABLE_ATTRIBUTE);
        if (true === $request->attributes->get(self::DISPATCHED_ATTRIBUTE)) {
            return;
        }
        if (!$throwable instanceof \Throwable) {
            $throwable = new \RuntimeException(\sprintf(
                'La requête a produit une réponse HTTP %d sans exception associée.',
                $statusCode,
            ));
        }

        $request->attributes->set(self::DISPATCHED_ATTRIBUTE, true);

        try {
            if (!$this->configuration->isReady()) {
                return;
            }

            $this->messageBus->dispatch(new DeliverErrorReportMessage(
                $this->reportFactory->create($throwable, $request, $statusCode),
            ));
        } catch (\Throwable $reportingFailure) {
            $this->logger?->warning('La mise en file de l’erreur HTTP vers le hub a échoué.', [
                'reporting_exception' => $reportingFailure::class,
            ]);
        }
    }
}
