<?php

declare(strict_types=1);

namespace App\Security\Voter\EntityVoter\Core;

use App\Entity\Access\Access;
use App\Entity\Core\File;
use App\Enum\Core\FileTypeEnum;
use App\Enum\Core\FileVisibilityEnum;
use App\Security\Voter\EntityVoter\AbstractEntityVoter;
use App\Service\Utils\DatesUtils;

/**
 * Contrôle l'accès à l'entité File.
 */
class FileVoter extends AbstractEntityVoter
{
    protected static ?string $entityClass = File::class;

    /**
     * @param $subject File
     */
    protected function canView(object $subject): bool
    {
        // Le fichier n'est pas encore disponible
        if (!$this->isAvailable($subject, $this->getUser())) {
            // TODO: à revoir
            return false;
        }

        // File has public visibility
        if ($subject->getVisibility() === FileVisibilityEnum::EVERYBODY) {
            return true;
        }

        // Is this an internal request?
        if ($this->isValidInternalRequest()) {
            return true;
        }

        // If the user is not logged in, the file is not available
        if ($this->isUserLoggedIn()) {
            // If the logged user is in the accessPersons of the File
            if (
                $subject->getAccessPersons()->contains($this->getUser()->getPerson())
            ) {
                return true;
            }

            // User is the owner of the file
            if ($subject->getPerson() && $subject->getPerson()->getId() === $this->getUser()->getPerson()->getId()) {
                return true;
            }

            // User has ROLE_FILE and owns the file or belongs to the organization who does
            if (
                $this->accessUtils->hasRole($this->getUser(), 'ROLE_FILE')
                && $this->isInFileOwningGroup($subject, $this->getUser())
            ) {
                return true;
            }

            // User has the requested role and owns the file or belongs to the organization who does (deprecated)
            if (
                $this->isInFileOwningGroupWithRole($subject, $this->getUser())
            ) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param $subject File
     */
    protected function canEdit(object $subject): bool
    {
        // Le fichier n'est pas encore disponible
        if (!$this->isAvailable($subject, $this->getUser())) {
            return false;
        }

        if ($this->isUserLoggedIn()) {
            // User has ROLE_FILE and has access to the file
            if (
                $this->accessUtils->hasRole($this->getUser(), 'ROLE_FILE')
                && $this->isInFileOwningGroup($subject, $this->getUser())
            ) {
                return true;
            }

            // User is the owner of the file
            if ($subject->getPerson() && $subject->getPerson()->getId() === $this->getUser()->getPerson()->getId()) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param $subject File
     */
    protected function canCreate(object $subject): bool
    {
        // An authenticated user can create a file
        if ($this->isUserLoggedIn()) {
            return true;
        }

        return false;
    }

    /**
     * @param $subject File
     */
    protected function canDelete(object $subject): bool
    {
        return $this->canEdit($subject);
    }

    /**
     * Is the given user the owner of the file, or does he belong to the organization that owns it, and does the user
     * have any of the required role to access this file?
     */
    protected function isInFileOwningGroup(File $file, Access $user): bool
    {
        $isOrganizationOwned = $file->getOrganization() && $file->getOrganization()->getId() === $user->getOrganization()->getId();
        $isAccessOwned = $file->getPerson() && $file->getPerson()->getId() === $user->getPerson()->getId();

        return $isOrganizationOwned || $isAccessOwned;
    }

    /**
     * Is the given user the owner of the file, or does he belong to the organization that owns it, and does the user
     * have any of the required role to access this file?
     */
    protected function isInFileOwningGroupWithRole(File $file, Access $user): bool
    {
        $requiredRoles = $file->getAccessRoles();
        if ($requiredRoles->count() === 0) {
            return false;
        }

        $userHasRequiredRole = $file->getAccessRoles()->exists(
            // TODO: implémenter une méthode hasAnyRoleAmong() côté accessUtils
            fn ($aR) => $this->accessUtils->hasRole($user, $aR)
        );

        return $this->isInFileOwningGroup($file, $user) && $userHasRequiredRole;
    }

    /**
     * Is the file available now?
     * Depends on the availabilityDate, the type of the file and the user.
     *
     * @param $file File
     *
     * @throws \Exception
     */
    protected function isAvailable(File $file, ?Access $user): bool
    {
        $today = DatesUtils::new();

        if ($file->getAvailabilityDate() !== null && $file->getAvailabilityDate() > $today) {
            //  Cas particulier de la liste des factures
            // TODO: clarifier le ou les cas particulier(s) et les sortir de cette méthode (celle ci devrait tenir en deux lignes max)
            if (
                $user
                && $file->getType() === FileTypeEnum::BILL
                && $this->accessUtils->hasRole($user, 'ROLE_BILLACCOUNTING')
            ) {
                return true;
            }

            return false;
        }

        return true;
    }
}
