<?php

namespace App\Shared\Tenant;

use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;

/**
 * جداسازی خودکار محیط: به هر کوئری DQL روی entityهای tenant-دار شرط
 * (entity_type, entity_id) اضافه می‌شود.
 *
 * پیش‌فرض خاموش است و فقط وقتی روشن می‌شود که محیط کاربر حل شده باشد
 * ({@see TenantFilterSubscriber}). مسیرهای عمومی مارکت‌پلیس و ادمین سراسری
 * عمداً بیرون از آن می‌مانند.
 *
 * ⚠️ تور ایمنی است، نه جایگزین authorization:
 *
 * | مسیر                                   | اعمال می‌شود؟ |
 * |----------------------------------------|---------------|
 * | DQL و QueryBuilder                     | ✅            |
 * | findBy / findOneBy                     | ✅            |
 * | EntityManager::find() با کلید اصلی     | ✅ (در ORM ۳؛ تثبیت‌شده در TenantFilterLeakTest) |
 * | بارگذاری تنبل کالکشن‌ها                 | ✅            |
 * | entity که از قبل در identity map است   | ❌ — دوباره کوئری نمی‌شود |
 * | getReference()                         | ❌            |
 * | SQL خام DBAL                           | ❌            |
 * | فرزندان aggregate (بدون ستون tenant)    | ❌ — از ریشه JOIN کن |
 *
 * مقداردهیِ proxy به موجودیتی که فیلتر کنارش گذاشته، EntityNotFoundException
 * می‌دهد؛ ExceptionSubscriber آن را به ۴۰۴ نگاشت می‌کند («بیرون از محیط تو» یعنی
 * «برای تو وجود ندارد»).
 *
 * پس AppointmentAccessChecker، ClinicDoctorAccessChecker، SecretaryAccessChecker
 * و PatientRecordScopeResolver سر جایشان می‌مانند: آن‌ها «چه کاری مجاز است» را
 * جواب می‌دهند، این فیلتر فقط «کدام ردیف‌ها».
 */
final class TenantFilter extends SQLFilter
{
    public const NAME = 'tenant';

    public const PARAM_TYPE = 'tenant_entity_type';
    public const PARAM_ID   = 'tenant_entity_id';

    public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
    {
        if (!$targetEntity->hasField('entityType') || !$targetEntity->hasField('entityId')) {
            return '';
        }

        return sprintf(
            '%s.%s = %s AND %s.%s = %s',
            $targetTableAlias,
            $targetEntity->getColumnName('entityType'),
            $this->getParameter(self::PARAM_TYPE),
            $targetTableAlias,
            $targetEntity->getColumnName('entityId'),
            $this->getParameter(self::PARAM_ID),
        );
    }
}
