<?php

declare(strict_types=1);

namespace App\Service\Access;

use App\Entity\Access\Access;
use App\Entity\Organization\Organization;
use App\Enum\Access\FunctionEnum;
use App\Repository\Access\AccessRepository;
use App\Service\ServiceIterator\OptionalsRolesIterator;
use App\Service\Utils\DatesUtils;
use App\Test\Service\Access\UtilsTest;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;

/**
 * Class Utils : service rassemblant des fonctions d'aides pour les questions se rapportant à l'access.
 */
class Utils
{
    public function __construct(
        readonly private RoleHierarchyInterface $roleHierarchy,
        readonly private AccessRepository $accessRepository,
        readonly private OptionalsRolesIterator $optionalsRolesIterator,
    ) {
    }

    /**
     * Filtre un tableau d'Access pour ne laisser que les Accesses ne correspondant pas à l'Access passé en second parametre.
     *
     * @param array<Access> $accesses
     *
     * @return array<Access>
     *
     * @see UtilsTest::testFilterAccesses()
     * TODO: est-ce qu'on essaierait pas de trouver un nom plus explicite? j'ai pas trop d'idée la tout de suite cela dit
     */
    public function filterAccesses(array $accesses, Access $access): array
    {
        return array_filter($accesses, function ($a) use ($access) {
            /* @var Access $a */
            return $a->getId() !== $access->getId();
        });
    }

    /**
     * Recherche parmis les roles si l'Access possède celui passé en paramètre.
     *
     * @see UtilsTest::testHasRoles()
     */
    public function hasRole(Access $access, string $roleToHave): bool
    {
        return in_array($roleToHave, $this->getAllRoles($access), true);
    }

    /**
     * Va récupérer les roles d'access + les roles optionnels possibles.
     *
     * @return string[]
     *
     * @see UtilsTest::testGetAllRoles()
     */
    public function getAllRoles(Access $access): array
    {
        // TODO: est-ce qu'on ne pourrait pas mettre ça en cache d'une façon ou d'une autre?
        $roles = $this->optionalsRolesIterator->getOptionalsRoles($access);

        return $this->roleHierarchy->getReachableRoleNames(array_merge($access->getRoles(), $roles));
    }

    /**
     * Renvoie l'access de l'organization qui est le "super admin".
     *
     * @see UtilsTest::testGetAdminAccess()
     */
    public function findAdminFor(Organization $organization): ?Access
    {
        return $this->accessRepository->findOneBy([
            'adminAccess' => true,
            'organization' => $organization,
        ]);
    }

    /**
     * Retourne les noms des fonctions actives d'un access au sein d'une organisation.
     *
     * @return array<string>
     *
     * @throws \Exception
     */
    public function getActiveFunctions(Access $access): array
    {
        $functions = [];

        foreach ($access->getOrganizationFunction() as $function) {
            if (
                $function->getStartDate() !== null
                && DatesUtils::new() <= $function->getStartDate()->sub(new \DateInterval('PT23H59M'))
            ) {
                // La fonction n'est pas encore active
                continue;
            }
            if (
                $function->getEndDate() !== null
                && DatesUtils::new() >= $function->getEndDate()->add(new \DateInterval('PT23H59M'))
            ) {
                // La fonction n'est plus active
                continue;
            }

            $functions[] = $function->getFunctionType()->getMission();
        }

        return $functions;
    }

    /**
     * L'access possède-t-il la fonction donnée au sein de son organisation.
     *
     * @throws \Exception
     */
    public function hasActiveFunction(Access $access, FunctionEnum $functionName): bool
    {
        return in_array($functionName->value, $this->getActiveFunctions($access));
    }
}
