<?php

namespace App\Staff\Security;

use App\Auth\Entity\User;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * کاربری که «فقط» پرسنل است، به هیچ مسیر API جز allowlist دسترسی ندارد.
 *
 * بیشتر کنترلرها tenant را از EntityContextResolver می‌گیرند و مجوز را فقط برای
 * منشی و پزشکِ مهمان می‌سنجند (SecretaryAccessChecker و ClinicDoctorAccessChecker
 * برای بقیهٔ نقش‌ها no-op هستند). حالا که محیطِ پرسنل هم حل می‌شود، بدون این گارد
 * یک کاربر پرسنل به دادهٔ کل کلینیک می‌رسید. تصمیم عمداً در یک نقطه متمرکز است تا
 * کنترلر جدید هم به‌صورت پیش‌فرض بسته باشد، نه اینکه یادمان برود deny اضافه کنیم.
 */
class StaffRouteGuardSubscriber implements EventSubscriberInterface
{
    /** مسیرهایی که نقش پرسنل مجاز است صدا بزند. */
    private const ALLOWED_PREFIXES = [
        '/api/v1/dashboard/staff',
        '/api/v1/auth/switch-context',
        '/api/v1/user/change-password',
    ];

    /**
     * نقش‌هایی که اگر کاربر یکی‌شان را داشته باشد، این گارد کنار می‌رود: کاربر
     * علاوه بر پرسنل بودن، نقش پرتوان‌تری هم دارد و محدودهٔ دسترسی‌اش را همان
     * نقش تعیین می‌کند.
     */
    private const OVERRIDING_ROLES = [
        'ROLE_ADMIN', 'ROLE_CLINIC', 'ROLE_DOCTOR', 'ROLE_SECRETARY', 'ROLE_REPRESENTATION',
    ];

    public function __construct(private readonly Security $security) {}

    public static function getSubscribedEvents(): array
    {
        // بعد از firewall (priority 8) تا توکن ست شده باشد.
        return [KernelEvents::REQUEST => ['onKernelRequest', 6]];
    }

    public function onKernelRequest(RequestEvent $event): void
    {
        if (!$event->isMainRequest()) {
            return;
        }

        $path = $event->getRequest()->getPathInfo();
        if (!str_starts_with($path, '/api/')) {
            return;
        }

        $user = $this->security->getUser();
        if (!$user instanceof User || !$this->isStaffOnly($user)) {
            return;
        }

        foreach (self::ALLOWED_PREFIXES as $prefix) {
            if (str_starts_with($path, $prefix)) {
                return;
            }
        }

        throw new AppException(ErrorCodes::ERR_FORBIDDEN_001, 'دسترسی پرسنل به این بخش مجاز نیست', 403);
    }

    private function isStaffOnly(User $user): bool
    {
        if (!$user->hasRole('ROLE_STAFF')) {
            return false;
        }

        foreach (self::OVERRIDING_ROLES as $role) {
            if ($user->hasRole($role)) {
                return false;
            }
        }

        return true;
    }
}
