<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\DocStoreBundle\Security\Authorization;

use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;

/**
 * Voter for the content of a stored object.
 *
 * This is in use to allow or disallow the edition of the stored object's content.
 */
class StoredObjectVoter extends Voter
{
    public const LOG_PREFIX = '[stored object voter] ';

    public function __construct(
        private readonly Security $security,
        private readonly iterable $storedObjectVoters,
        private readonly LoggerInterface $logger,
        private readonly EntityWorkflowAttachmentRepository $entityWorkflowAttachmentRepository,
    ) {}

    protected function supports($attribute, $subject): bool
    {
        return StoredObjectRoleEnum::tryFrom($attribute) instanceof StoredObjectRoleEnum
            && $subject instanceof StoredObject;
    }

    protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
    {
        /** @var StoredObject $subject */
        $attributeAsEnum = StoredObjectRoleEnum::from($attribute);

        // check if the stored object is attached to any workflow
        $user = $token->getUser();
        if ($user instanceof User && StoredObjectRoleEnum::SEE === $attributeAsEnum) {
            foreach ($this->entityWorkflowAttachmentRepository->findByStoredObject($subject) as $workflowAttachment) {
                if ($workflowAttachment->getEntityWorkflow()->isUserInvolved($user)) {
                    return true;
                }
            }
        }

        // Loop through context-specific voters
        foreach ($this->storedObjectVoters as $storedObjectVoter) {
            if ($storedObjectVoter->supports($attributeAsEnum, $subject)) {
                $grant = $storedObjectVoter->voteOnAttribute($attributeAsEnum, $subject, $token);

                if (false === $grant) {
                    $this->logger->debug(self::LOG_PREFIX.'deny access by storedObjectVoter', ['stored_object_voter' => $storedObjectVoter::class]);
                }

                return $grant;
            }
        }

        // User role-based fallback
        if ($this->security->isGranted('ROLE_USER') || $this->security->isGranted('ROLE_ADMIN')) {
            // TODO: this maybe considered as a security issue, as all authenticated users can reach a stored object which
            // is potentially detached from an existing entity.
            return true;
        }

        return false;
    }
}
