<?php

namespace KupShop\KupShopBundle\Util\Event;

use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
use KupShop\KupShopBundle\Exception\ErrorIgnoringEventException;
use Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;

class ErrorIgnoringEventDispatcher implements EventDispatcherInterface
{
    private $eventDispatcher;

    public function __construct(EventDispatcherInterface $eventDispatcher)
    {
        $this->eventDispatcher = $eventDispatcher;
    }

    protected function doDispatch(iterable $listeners, string $eventName, Event $event): void
    {
        $stoppable = $event instanceof StoppableEventInterface;

        foreach ($listeners as $listener) {
            if ($stoppable && $event->isPropagationStopped()) {
                break;
            }

            try {
                $listener($event, $eventName, $this);
            } catch (\Throwable $e) {
                if (isDevelopment()) {
                    throw $e;
                }

                $data = [];
                if (($e instanceof DeadlockException) || ($e instanceof LockWaitTimeoutException)) {
                    $data['extra'] = json_encode(sqlQuery('SHOW FULL PROCESSLIST')->fetchAllAssociative());
                }

                $sentry = getRaven();
                $sentry->captureException(
                    new ErrorIgnoringEventException($this->getListenerName($listener).': '.$e->getMessage(), intval($e->getCode()), $e),
                    $data
                );
            }
        }
    }

    public function addListener(string $eventName, callable $listener, int $priority = 0): void
    {
        $this->eventDispatcher->addListener($eventName, $listener, $priority);
    }

    public function addSubscriber(EventSubscriberInterface $subscriber): void
    {
        $this->eventDispatcher->addSubscriber($subscriber);
    }

    public function removeListener(string $eventName, callable $listener): void
    {
        $this->eventDispatcher->removeListener($eventName, $listener);
    }

    public function removeSubscriber(EventSubscriberInterface $subscriber): void
    {
        $this->eventDispatcher->removeSubscriber($subscriber);
    }

    public function getListeners(?string $eventName = null): array
    {
        return $this->eventDispatcher->getListeners($eventName);
    }

    public function dispatch(object $event, ?string $eventName = null): object
    {
        if (null === $event) {
            $event = new Event();
        }

        $eventName = $eventName ?? \get_class($event);

        if ($listeners = $this->getListeners($eventName)) {
            $this->doDispatch($listeners, $eventName, $event);
        }

        return $event;
    }

    public function getListenerPriority(string $eventName, callable $listener): ?int
    {
        return $this->eventDispatcher->getListenerPriority($eventName, $listener);
    }

    public function hasListeners(?string $eventName = null): bool
    {
        return $this->eventDispatcher->hasListeners($eventName);
    }

    private function getListenerName($listener): string
    {
        $name = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0];

        return $name.'::'.$listener[1];
    }
}
