<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Security;

use App\Shared\Infrastructure\Tenant\TenantContext;
use App\Shared\Infrastructure\Tenant\TenantNotSetException;
use Override;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * Voter that enforces tenant isolation at the authorization level.
 *
 * CRITICAL SECURITY: This voter is the second line of defense (after TenantMiddleware).
 * It prevents authenticated users from accessing resources belonging to other tenants.
 *
 * IMPORTANT: This voter ONLY handles the TENANT_ACCESS attribute to avoid interfering
 * with other authorization checks (ROLE_*, EDIT, DELETE, etc.). Use isGranted('TENANT_ACCESS', $entity)
 * explicitly when you need to verify tenant ownership.
 *
 * Returns ACCESS_DENIED (which is converted to 404 by AccessDeniedHandler) to avoid
 * revealing the existence of resources in other tenants.
 *
 * @extends Voter<string, TenantAwareInterface>
 */
final class TenantVoter extends Voter
{
    public const string ATTRIBUTE = 'TENANT_ACCESS';

    public function __construct(
        private readonly TenantContext $tenantContext,
    ) {
    }

    #[Override]
    protected function supports(string $attribute, mixed $subject): bool
    {
        // Only vote on TENANT_ACCESS attribute to avoid bypassing other voters
        return $attribute === self::ATTRIBUTE && $subject instanceof TenantAwareInterface;
    }

    #[Override]
    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
    {
        $user = $token->getUser();

        // User must be authenticated
        if (!$user instanceof UserInterface) {
            return false;
        }

        // Subject must be tenant-aware (should always be true due to supports())
        if (!$subject instanceof TenantAwareInterface) {
            return false;
        }

        // Tenant context must be set
        try {
            $currentTenantId = $this->tenantContext->getCurrentTenantId();
        } catch (TenantNotSetException) {
            // No tenant context - deny access
            return false;
        }

        // CRITICAL: Verify resource belongs to current tenant
        // If resource belongs to different tenant, return false (-> 404)
        return $subject->getTenantId()->equals($currentTenantId);
    }
}
