<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Tenant;

use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Ferienpass\CoreBundle\Entity\TenantAwareInterface;
use Symfony\Component\DependencyInjection\Attribute\Exclude;

/**
 * The global tenant filter: while it is enabled, every SELECT Doctrine emits
 * against a {@see TenantAwareInterface} entity — a find(), a DQL query, a
 * lazily loaded association — is constrained to the active tenant.
 *
 * Doctrine instantiates this itself, so it is not a service (hence #[Exclude]);
 * it is enabled and parameterised exclusively through {@see TenantContext}.
 */
#[Exclude]
final class TenantFilter extends SQLFilter
{
    public const NAME = 'tenant';

    public const PARAMETER = 'tenant_id';

    public function addFilterConstraint(ClassMetadata $targetEntity, string $targetTableAlias): string
    {
        if (!$targetEntity->getReflectionClass()->implementsInterface(TenantAwareInterface::class)) {
            return '';
        }

        // With single-table inheritance every subclass shares the root's table;
        // constraining the root alone is both sufficient and correct.
        if ($targetEntity->isInheritanceTypeSingleTable() && $targetEntity->rootEntityName !== $targetEntity->name) {
            return '';
        }

        if (!$targetEntity->hasAssociation('tenant')) {
            return '';
        }

        try {
            $tenantId = $this->getParameter(self::PARAMETER);
        } catch (\InvalidArgumentException) {
            // Enabled without a tenant should be impossible — TenantContext sets
            // the parameter in the same breath as it enables the filter. If it
            // ever happens, hide everything rather than expose everything.
            return '1 = 0';
        }

        return \sprintf('%s.%s = %s', $targetTableAlias, $targetEntity->getSingleAssociationJoinColumnName('tenant'), $tenantId);
    }
}
