<?php

namespace App\Shared\EventSubscriber;

use App\Shared\Exception\AppException;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;

class ExceptionSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private readonly LoggerInterface    $logger,
        private readonly TokenStorageInterface $tokenStorage,
    ) {}

    public function onKernelException(ExceptionEvent $event): void
    {
        if (str_starts_with($event->getRequest()->getPathInfo(), '/api/doc')) {
            return;
        }

        $exception = $event->getThrowable();

        if ($exception instanceof AppException) {
            $err = ['code' => $exception->getErrorCode(), 'message' => $exception->getMessage()];
            if ($exception->getField()) {
                $err['field'] = $exception->getField();
            }
            $event->setResponse(new JsonResponse(
                ['success' => false, 'data' => null, 'errors' => [$err]],
                $exception->getHttpStatus()
            ));
            return;
        }

        if ($exception instanceof TooManyRequestsHttpException) {
            $headers = [];
            $retryAfter = $exception->getHeaders()['Retry-After'] ?? null;
            if ($retryAfter !== null) {
                $headers['Retry-After'] = $retryAfter;
            }
            $event->setResponse(new JsonResponse(
                ['success' => false, 'data' => null, 'errors' => [['code' => 'ERR_RATE_LIMIT_001', 'message' => 'درخواست‌های زیاد. لطفاً بعداً تلاش کنید']]],
                429,
                $headers
            ));
            return;
        }

        if ($exception instanceof NotFoundHttpException) {
            $event->setResponse(new JsonResponse(
                ['success' => false, 'data' => null, 'errors' => [['code' => 'ERR_NOT_FOUND_001', 'message' => 'منبع درخواستی یافت نشد']]],
                404
            ));
            return;
        }

        if ($exception instanceof AccessDeniedHttpException) {
            $event->setResponse(new JsonResponse(
                ['success' => false, 'data' => null, 'errors' => [['code' => 'ERR_FORBIDDEN_001', 'message' => 'دسترسی به این منبع مجاز نیست']]],
                403
            ));
            return;
        }

        if ($exception instanceof UnauthorizedHttpException) {
            $event->setResponse(new JsonResponse(
                ['success' => false, 'data' => null, 'errors' => [['code' => 'ERR_AUTH_001', 'message' => 'احراز هویت لازم است']]],
                401
            ));
            return;
        }

        // Security exceptions not yet wrapped into HttpException
        if ($exception instanceof AccessDeniedException) {
            $token = $this->tokenStorage->getToken();
            $isAuthenticated = $token !== null && $token->getUser() !== null;

            if (!$isAuthenticated) {
                $event->setResponse(new JsonResponse(
                    ['success' => false, 'data' => null, 'errors' => [['code' => 'ERR_AUTH_001', 'message' => 'احراز هویت لازم است']]],
                    401
                ));
            } else {
                $event->setResponse(new JsonResponse(
                    ['success' => false, 'data' => null, 'errors' => [['code' => 'ERR_FORBIDDEN_001', 'message' => 'دسترسی به این منبع مجاز نیست']]],
                    403
                ));
            }
            return;
        }

        if ($exception instanceof AuthenticationException) {
            $event->setResponse(new JsonResponse(
                ['success' => false, 'data' => null, 'errors' => [['code' => 'ERR_AUTH_001', 'message' => 'احراز هویت لازم است']]],
                401
            ));
            return;
        }

        // Generic fallback: never leak stack traces or internal details in API responses
        $this->logger->error('Unhandled exception', [
            'exception' => $exception,
            'path'      => $event->getRequest()->getPathInfo(),
        ]);

        $event->setResponse(new JsonResponse(
            ['success' => false, 'data' => null, 'errors' => [['code' => 'ERR_INTERNAL_001', 'message' => 'خطای داخلی سرور']]],
            500
        ));
    }

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