<?php
declare(strict_types=1);

namespace App\Service\Security;

use App\Entity\Organization\Organization;
use App\Service\Utils\Reflection;
use Doctrine\Common\Cache\ApcuCache;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

/**
 * Class Module : classe gérant la récupération de l'ensemble des modules possédées par une organisation
 * @package App\Service\Security
 */
class Module
{
    public function __construct(
        readonly private Reflection $reflection,
        readonly private ParameterBagInterface $parameterBag
    ) {}

    /**
     * @todo activer le cache après que la fin de la migration.
     * Récupère tous les modules de l'organisation
     * @param Organization $organization
     * @return list<string>
     */
    public function getOrganizationModules(Organization $organization): array
    {
//        $cacheDriver = new ApcuCache();
//        //If the modules are all ready available inside the APCu cache
//        if($cacheDriver->contains('organization_modules_' . $organization->getId())){
//            return $cacheDriver->fetch('organization_modules_' . $organization->getId());
//        }

        // Get modules from the organization settings
        $modulesBySettings = $this->getModuleBySettings($organization);

        // Get modules according to the conditions defined in the config
        $modulesByConditions = $this->getModulesByConditions($organization);

        // Get the modules from the owned product
        $product = $organization->getSettings()->getProduct();
        $modulesForProduct = $this->getModulesByProductConfiguration($product);

        // Controls that no modules where setup in different locations (this should not happen, and can lead to unexpected
        // behaviors whend serializing the OrganizationProfile)
        $intersection = array_merge(
            array_intersect($modulesBySettings, $modulesByConditions),
            array_intersect($modulesByConditions, $modulesForProduct),
            array_intersect($modulesBySettings, $modulesForProduct)
        );
        if (!empty($intersection)) {
            throw new \RuntimeException("Those modules are defined more than once : " . implode(', ', $intersection));
        }

        $organizationModules = array_merge_recursive($modulesForProduct, $modulesBySettings, $modulesByConditions);

//        //Keep the modules inside the APCu cache
//        $cacheDriver->save('organization_modules_' . $organization->getId(), $organizationModules, '86400');

        return $organizationModules;
    }

    /**
     * Récupère les modules disponibles dans les settings de l'organisation (Sms, Pes, etc)
     * @param Organization $organization
     * @return list<string>
     * @see ModuleTest::testGetModuleBySettings()
     */
    public function getModuleBySettings(Organization $organization): array
    {
        $moduleByOptions = [];
        $modulesOptions = $organization->getSettings()->getModules();
        if(!empty($modulesOptions)){
            foreach($modulesOptions as $moduleOptionName => $moduleOptionValue){
                if($moduleOptionValue)
                    $moduleByOptions[] = $moduleOptionName;
            }
        }
        return $moduleByOptions;
    }

    /**
     * Récupère les modules par conditions, grace au fichier modulebyconditions.yaml
     * @param Organization $organization
     * @return list<string>
     * @see ModuleTest::testGetModulesByConditions()
     */
    public function getModulesByConditions(Organization $organization): array
    {
        $modulesByConditions = [];
        $modules = $this->parameterBag->get('opentalent.modulesbyconditions');
        foreach ($modules as $moduleName => $module) {
            try {
                $service = $module["conditions"]["service"];
                $args = $service['args'] ?? [];

                $response = $this->reflection->dynamicInvokeServiceWithArgsAndMethod(
                    $service["name"],
                    $service["function"],
                    array_merge([$organization], $args)
                );
                if ($response) {
                    $modulesByConditions[] = $moduleName;
                }
            } catch (\Exception $exception){
                throw new \LogicException(
                    $exception->getMessage(),
                    400,
                    $exception
                );
            }
        }
        return $modulesByConditions;
    }

    /**
     * Récupère les modules disponibles par produit grace au fichier products.yaml
     * @param string $product
     * @return list<string>|null
     * @see ModuleTest::testGetModulesByProductConfiguration()
     */
    public function getModulesByProductConfiguration(string $product): ?array
    {
        $product = str_replace('-', '_', $product);
        $opentalentProducts = $this->parameterBag->get('opentalent.products');

        if (!array_key_exists($product, $opentalentProducts)) {
            throw new AccessDeniedHttpException(sprintf('The product %s does not exist !', $product));
        }
        $productConfig = $opentalentProducts[$product];
        $modules = $productConfig['modules'] ?? [];

        if (array_key_exists('extend', $productConfig)) {
            $modules = array_merge($modules, $this->getModulesByProductConfiguration($productConfig['extend']));
            unset($productConfig['extend']);
        }

        return $modules;
    }

    /**
     * Retourne le module possédant la resource passée en paramètre
     * @param string $resource
     * @return int|null|string
     */
    public function getModuleByResourceName(string $resource): int|string|null
    {
        $modules = $this->parameterBag->get('opentalent.modules');
        foreach ($modules as $module => $data) {
            if ($data['entities'] && in_array($resource, $data['entities'], true)) {
                return $module;
            }
        }
        return null;
    }
}
