<?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\StoredObjectVoter;

use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectVoterInterface;
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;

abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
{
    abstract protected function getRepository(): AssociatedEntityToStoredObjectInterface;

    /**
     * @return class-string
     */
    abstract protected function getClass(): string;

    abstract protected function attributeToRole(StoredObjectRoleEnum $attribute): string;

    abstract protected function canBeAssociatedWithWorkflow(): bool;

    public function __construct(
        private readonly Security $security,
        private readonly ?WorkflowRelatedEntityPermissionHelper $workflowDocumentService = null,
    ) {}

    public function supports(StoredObjectRoleEnum $attribute, StoredObject $subject): bool
    {
        $class = $this->getClass();

        return $this->getRepository()->findAssociatedEntityToStoredObject($subject) instanceof $class;
    }

    public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool
    {
        // we first try to get the permission from the workflow, as attachement (this is the less intensive query)
        $workflowPermissionAsAttachment = match ($attribute) {
            StoredObjectRoleEnum::SEE => $this->workflowDocumentService->isAllowedByWorkflowForReadOperation($subject),
            StoredObjectRoleEnum::EDIT => $this->workflowDocumentService->isAllowedByWorkflowForWriteOperation($subject),
        };

        if (WorkflowRelatedEntityPermissionHelper::FORCE_DENIED === $workflowPermissionAsAttachment) {
            return false;
        }

        // Retrieve the related entity
        $entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject);

        // Determine the attribute to pass to the voter for argument
        $voterAttribute = $this->attributeToRole($attribute);

        $regularPermission = $this->security->isGranted($voterAttribute, $entity);

        if (!$this->canBeAssociatedWithWorkflow()) {
            return $regularPermission;
        }

        $workflowPermission = match ($attribute) {
            StoredObjectRoleEnum::SEE => $this->workflowDocumentService->isAllowedByWorkflowForReadOperation($entity),
            StoredObjectRoleEnum::EDIT => $this->workflowDocumentService->isAllowedByWorkflowForWriteOperation($entity),
        };

        return match ($workflowPermission) {
            WorkflowRelatedEntityPermissionHelper::FORCE_GRANT => true,
            WorkflowRelatedEntityPermissionHelper::FORCE_DENIED => false,
            WorkflowRelatedEntityPermissionHelper::ABSTAIN => WorkflowRelatedEntityPermissionHelper::FORCE_GRANT === $workflowPermissionAsAttachment || $regularPermission,
        };
    }
}
