<?php

declare(strict_types=1);

namespace App\EventSubscriber;

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Throwable;

final class UniqueConstraintSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::EXCEPTION => ['onKernelException', 256],
        ];
    }

    public function onKernelException(ExceptionEvent $event): void
    {
        $exception = $this->findUniqueConstraintViolation($event->getThrowable());

        if (!$exception) {
            return;
        }

        $constraint = $this->detectConstraintName($exception);
        $error      = match ($constraint) {
            'unique_category_name'   => 'Un élément avec ce nom existe déjà dans cette catégorie.',
            'uniq_machine_name_site' => 'Une machine avec ce nom existe déjà sur ce site.',
            default                  => 'Un élément avec cette valeur existe déjà.',
        };

        $event->setResponse(new JsonResponse(
            [
                'success'    => false,
                'error'      => $error,
                'constraint' => $constraint,
            ],
            JsonResponse::HTTP_CONFLICT
        ));
    }

    private function findUniqueConstraintViolation(Throwable $throwable): ?UniqueConstraintViolationException
    {
        for ($current = $throwable; null !== $current; $current = $current->getPrevious()) {
            if ($current instanceof UniqueConstraintViolationException) {
                return $current;
            }
        }

        return null;
    }

    private function detectConstraintName(UniqueConstraintViolationException $exception): ?string
    {
        $message = $exception->getMessage();

        if (preg_match('/constraint\s+"([^"]+)"/', $message, $matches)) {
            return $matches[1];
        }

        return null;
    }
}
