<?php
declare(strict_types=1);

namespace App\Service\Security;

use App\Entity\Organization\Organization;
use App\Service\Utils\Parser;
use App\Service\Utils\Reflection;
use Doctrine\Common\Cache\ApcuCache;
use Symfony\Component\Config\FileLocator;
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
{
    /**
     * @var mixed[]|null $moduleConfig
     */
    private array | null $moduleConfig = null;

    /**
     * @var mixed[]|null $moduleByConditionsConfig
     */
    private array | null $moduleByConditionsConfig = null;

    public function __construct(
        readonly private Reflection $reflection,
        readonly private Parser $parser,
        readonly private string $opentalentConfig)
    {}

    /**
     * @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());
//        }

        $modulesBySettings = $this->getModuleBySettings($organization);

        $modulesByConditions = $this->getModulesByConditions($organization);

        $product = $organization->getSettings()->getProduct();
        $modulesForProduct = $this->getModulesByProductConfiguration($product);

        $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->getModuleByConditionsConfig()['opentalent']['modulesbyconditions'];
        foreach ($modules as $moduleName => $module) {
            try{
                $response = $this->reflection->dynamicInvokeServiceWithArgsAndMethod(
                    $module["conditions"]["service"]["name"],
                    $module["conditions"]["service"]["function"],
                    array($organization)
                );
                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);

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

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

        return $modules;
    }

    /**
     * @return mixed[]
     * @throws \Exception
     */
    protected function getModuleConfig(): array {
        if ($this->moduleConfig === null) {
            $this->moduleConfig = $this->loadModuleConfig();
        }
        return $this->moduleConfig;
    }

    /**
     * Parse et retourne le contenu du fichier products.yaml
     * @return mixed
     * @throws \Exception
     */
    protected function loadModuleConfig(): mixed
    {
        return $this->parser->yamlParser($this->opentalentConfig, 'products.yaml');
    }

    /**
     * @return mixed[]
     * @throws \Exception
     */
    protected function getModuleByConditionsConfig(): array {
        if ($this->moduleByConditionsConfig === null) {
            $this->moduleByConditionsConfig = $this->loadModuleByConditionsConfig();
        }
        return $this->moduleByConditionsConfig;
    }

    /**
     * Parse et retourne le contenu du fichier modulesbyconditions.yaml
     * @return mixed
     * @throws \Exception
     */
    protected function loadModuleByConditionsConfig(): mixed
    {
        return $this->parser->yamlParser($this->opentalentConfig, 'modulesbyconditions.yaml');
    }

    /**
     * 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->getModuleConfig()['opentalent']['modules'];
        foreach ($modules as $module => $data) {
            if ($data['entities'] && in_array($resource, $data['entities'], true)) {
                return $module;
            }
        }
        return null;
    }
}
