<?php 

namespace PrestaShop\Module\LCVPrestaConnector\Controller;

use PrestaShop\Module\LCVPrestaConnector\AutoUpdater;
use PrestaShop\Module\LCVPrestaConnector\Forms\InformationType;
use PrestaShop\Module\LCVPrestaConnector\Forms\CatalogType;
use PrestaShop\Module\LCVPrestaConnector\Forms\ConfirmDialogType;
use PrestaShop\Module\LCVPrestaConnector\Forms\OrderType;
use PrestaShop\Module\LCVPrestaConnector\Forms\StocksType;
use Symfony\Component\HttpFoundation\Request;
use PrestaShop\Module\LCVPrestaConnector\SyncConfiguration;
use PrestaShop\Module\LCVPrestaConnector\Syncer;
use Symfony\Component\HttpFoundation\JsonResponse;

class ConfigurationAdminController extends AdminController
{
    /**
     * On enrechit la configuration de la barre d'outils
     * 
     * @return array
     */
    protected function getAdminLayoutToolBar() : array 
    {
        $toolbar = parent::getAdminLayoutToolBar();

        $mustLogin = $this->checkLogin();

        if ($mustLogin)
            $toolbar = [];

        $toolbar['mapping'] = [
                    'href' => $this->generateUrl($this->module->name . '_map_category'),
                    'class' => 'btn-outline-secondary pc-toolbar-button',
                    'desc' => 'Mappings',
                    'help' => 'Configurer les mappings',
                    'icon' => 'map',                    
                ];

        if (!$mustLogin)
            $toolbar['bind'] = [
                        'href' => $this->generateUrl($this->module->name . '_configuration') . '&reset-binding=1',
                        'class' => 'btn-outline-secondary pc-toolbar-button',
                        'desc' => 'Déconnecter',
                        'help' => '⚠⚠⚠ Déconnecte le module de votre backoffice. Vous devrez vous reconnecter pour continuer à utiliser le module.',
                        'icon' => 'cancel',                    
                    ];
        else
            $toolbar['bind'] = [
                'href' => $this->generateUrl($this->module->name . '_configuration') . '',
                'class' => 'btn-outline-secondary pc-toolbar-button',
                'desc' => 'Associer',
                'help' => 'Associe le module à votre backoffice '.$this->module->getBridge()->getId().'.',
                'icon' => 'login',
            ];

        return $toolbar;
    }

    /**
     * Convertit du markdown en HTML, en mode simplifié (on a pas besoin de plus)
     */
    private function markdownToHtml(string $markdown): string
    {
        // On commence l'analyse au premier titre (première caractère #)
        // avec un bon vieux strpos
        $pos = strpos($markdown, '#');
        if ($pos !== false)
            $markdown = substr($markdown, $pos);        

        // Convertir les titres
        $markdown = preg_replace('/^#### (.*)$/m', '<h4>$1</h4>', $markdown);
        $markdown = preg_replace('/^### (.*)$/m', '<h3>$1</h3>', $markdown);
        $markdown = preg_replace('/^## (.*)$/m', '<h2>$1</h2>', $markdown);
        $markdown = preg_replace('/^# (.*)$/m', '<h1>$1</h1>', $markdown);

        // Convertir le gras et l'italique
        $markdown = preg_replace('/\*\*(.*?)\*\*/', '<strong>$1</strong>', $markdown); // **texte**
        $markdown = preg_replace('/\*(.*?)\*/', '<em>$1</em>', $markdown);           // *texte*
        // ```code``` => <pre></pre>
        $markdown = preg_replace('/```(.*?)```/s', '<pre>$1</pre>', $markdown);
        // ```code``` => <code></code>
        $markdown = preg_replace('/`(.*?)`/s', '<code>$1</code>', $markdown);

        // Convertir les listes
//        $markdown = preg_replace('/^- (.*)$/m', '<li>$1</li>', $markdown);
//        $markdown = preg_replace('/^\* (.*)$/m', '<li>$1</li>', $markdown);

        // Convertir les listes Markdown en HTML
        $lines = explode("\n", $markdown);
        $html = '';
        $inList = false;

        foreach ($lines as $line) {
            $trimmed = trim($line);

            // Détecter une liste
            if (preg_match('/^\* (.+)$/', $trimmed, $matches)) {
                if (!$inList) {
                    $html .= "<ul>\n";
                    $inList = true;
                }
                $html .= "<li>{$matches[1]}</li>\n";
            } else {
                // Si on sort d'une liste, fermer la balise <ul>
                if ($inList) {
                    $html .= "</ul>\n";
                    $inList = false;
                }
                // Ajouter les autres lignes normalement
                $html .= $line . "\n";
            }
        }

        // S'assurer de fermer une liste ouverte en fin de fichier
        if ($inList) {
            $html .= "</ul>\n";
        }

        $markdown = $html;

        // Convertir les liens
        $markdown = preg_replace('/\[(.*?)\]\((.*?)\)/', '<a href="$2">$1</a>', $markdown); // [texte](url)

        // Convertir les sauts de ligne, que quand ils sont doublés
        $markdown = preg_replace('/\n\n/', '<br>', $markdown);
        
        return $markdown;
    }  

    /** 
     * Rendu du Changelog
     */
    public function changelogDisplayAction() {

        $doc = @file_get_contents($this->module->getLocalPath() . 'CHANGELOG.md');
        if (!$doc)
            $doc = "Le fichier CHANGELOG.md n'a pas pu être chargé.";

        // url de base
        $base_url = \Context::getContext()->shop->getBaseURL() . 'modules/'. $this->module->name . '/';

        // Chemin du PDF de documentation (généré lors du packaging du module).
        // En développement, le PDF n'existe pas : le template affiche alors un message d'indisponibilité.
        $doc_path = $this->module->getLocalPath() . 'docs/' . $this->module->BridgePrefix . '/LCVPrestaConnector.pdf';
        if (!file_exists($doc_path))
            $url_doc = "https://ftp.vega-net.net/dists/" . strtolower($this->module->getBridge()->getId()). "prestaconnector/stable/latest/LCVPrestaConnector.pdf";
        else            
            $url_doc = $base_url.'docs/'.$this->module->BridgePrefix.'/LCVPrestaConnector.pdf';

        $refreshed = false;
        if (isset($_REQUEST['refresh_update']) && $_REQUEST['refresh_update'])
        {
            $refreshed = true;

            $updater = new AutoUpdater($this->module);
            $updater->searchUpdate();
        }

        return $this->render('@Modules/' . $this->module->name . '/views/templates/admin/showdoc.html.twig', $this->addDefaultVars([
            'url_doc' => $url_doc,
            'pdf_exists' => file_exists($doc_path),
            'version' => $this->module->installed_version,
            'version_update' => $this->module->version != $this->module->installed_version ? $this->module->version : null,
            'refreshed' => $refreshed,
            'doc_html' => $this->markdownToHtml($doc)
        ]));
    }

    /**
     * Gestion du formulaire de configuration des informations
     */
    public function informationForm(Request $request)
    {        
        $form = $this->createForm(InformationType::class);
        
        $form->handleRequest($request);

        if($form->isSubmitted() && $form->isValid())
        {
            $cfg = $this->module->getCfg();

            // on enregistre directement dans la bdd si mode est vrai alors la valeur et à 0 sinon elle est à 1 
            $cfg->set(SyncConfiguration::CFG_ACTIVE,  $form->get("synchro_active")->getData() ? true : false ); 

            // on enregistre directement dans la bdd si mode est vrai alors la valeur et à 0 sinon elle est à 1 
            $cfg->set(SyncConfiguration::CFG_WORKMODE, $form->get("cfg_mode")->getData() ? 0 : 1 ); 

            // on enregistre directement dans la bdd
            $photo_clean = $form->get("photo_clean")->getData();
            // on vérifie que photo_clean est un entier positif ou nul
            if (!is_numeric($photo_clean) || $photo_clean < 0)
                $photo_clean = 0;

            $cfg->set(SyncConfiguration::CFG_PHOTO_CLEAN, $photo_clean ?? 0 ); 

            // mémoire maximale
            $cfg->set(SyncConfiguration::CFG_MEMORY_MAX_SIZE, $form->get("memory_size")->getData() ?? '-1' );

            // délai d'attente des appels API (en secondes) : entier positif, défaut 600
            $apiTimeout = (int) $form->get("api_timeout")->getData();
            if ($apiTimeout < 1)
                $apiTimeout = 600;
            $cfg->set(SyncConfiguration::CFG_API_TIMEOUT, $apiTimeout);

            $cfg->set(SyncConfiguration::CFG_OIDC_ENABLED, $form->get('oidc_enabled')->getData() ? true : false);


            // token guard ?
            $guard = $form->get("guard_token")->getData();
            // On remplace les caractères "'" par un espace vide
            $guard = str_replace("'", "", $guard);
            // Regenération du token si égal à "new"
            if ($guard === 'new')
            {
                // Génération d'un token aléatoire
                $guard = rtrim(base64_encode(random_bytes(32)), '=');
            }

            $cfg->set(SyncConfiguration::CFG_GUARD_TOKEN, $guard);

            // Intervales de synchronisation
            $cfg->set(SyncConfiguration::CFG_SYNC_PRODUCT_INTERVAL, $form->get("sync_product_interval")->getData() );
            $cfg->set(SyncConfiguration::CFG_SYNC_STOCK_INTERVAL, $form->get("sync_stock_interval")->getData() );
            $cfg->set(SyncConfiguration::CFG_SYNC_FULL_PRODUCT_INTERVAL, $form->get("sync_full_product_interval")->getData() );
            $cfg->set(SyncConfiguration::CFG_SYNC_FULL_STOCK_INTERVAL, $form->get("sync_full_stock_interval")->getData() );

            $this->addFlash('success', 'Configuration sauvegardée !');

            // On recharge le formulaire pour éviter de garder des données non corrigée à l'affichage
            $form = $this->createForm(InformationType::class);
        }

        return $form;

    }

    /**
     * Gestion du formulaire de configuration des commandes
     */
    public function orderForm(Request $request)
    {
        $cfg =  $this->module->getCfg();

        // Auto-détection des moyens de paiements manquants...
        // On tire la liste des moyens de paiements (payment_method) de la table order_payment
        $db = \Db::getInstance();
        $sql = 'SELECT DISTINCT payment_method FROM '._DB_PREFIX_.'order_payment';
        $result = $db->executeS($sql);
        if ($result === false)
        {
            $this->addFlash('error', "Erreur lors de la récupération des moyens de paiements : " . $db->getMsgError());
        }
        else
        {
            // On charge les moyens de paiements connus
            $knownPayments = $cfg->get(SyncConfiguration::CFG_ORDER_PAY_MAPPAGE) ?? [];

            // On ajoute les moyens de paiements manquants
            $result[] = [ 'payment_method' => '*' ];
            foreach ($result as $row)
            {
                $payment = $row['payment_method'];
                if (!isset($knownPayments[$payment]))
                {
                    // On ajoute la clé manquante à la configuration
                    $cfg->setMap(SyncConfiguration::CFG_ORDER_PAY_MAPPAGE, $payment, '');
                }
            }
        }

        $form = $this->createForm(OrderType::class);

        $form->handleRequest($request);

        if($form->isSubmitted() && $form->isValid())
        {
            $data = $form->getData();

            /** Options de commandes */
            if (isset($data[SyncConfiguration::CFG_ORDER_EXPORT_REFUND]))
                $cfg->set(SyncConfiguration::CFG_ORDER_EXPORT_REFUND, (bool) ($data[SyncConfiguration::CFG_ORDER_EXPORT_REFUND] ?? false));

            if (isset($data[SyncConfiguration::CFG_ORDER_BARRIER]) && is_numeric($data[SyncConfiguration::CFG_ORDER_BARRIER]))
                $cfg->set(SyncConfiguration::CFG_ORDER_BARRIER, intval($data[SyncConfiguration::CFG_ORDER_BARRIER]) ?? 0);

            if (isset($data[SyncConfiguration::CFG_ORDER_MAX_TIME]) && is_numeric($data[SyncConfiguration::CFG_ORDER_MAX_TIME]))
                $cfg->set(SyncConfiguration::CFG_ORDER_MAX_TIME, intval($data[SyncConfiguration::CFG_ORDER_MAX_TIME]) ?? 90);

            /** Export/Import client */
            if (isset($data['customer_import']))
                $cfg->set(SyncConfiguration::CFG_IMPORT_CUSTOMERS, (bool) ($data["customer_import"] ?? false));
            if (isset($data['customer_export']))
                $cfg->set(SyncConfiguration::CFG_EXPORT_CUSTOMERS, (bool) ($data["customer_export"] ?? false));
            if (isset($data['customer_export_newsletter']))
                $cfg->set(SyncConfiguration::CFG_EXPORT_CUSTOMERS_NEWSLETTER, (bool) ($data["customer_export_newsletter"] ?? false));

            // Référence à utiliser pour les frais de port et frais d'emballage
            if (isset($data['shipping_ref']))
                $cfg->set(SyncConfiguration::CFG_SHIPPING_COST_REF, $data["shipping_ref"] ?? 'shipping');

            if (isset($data['wrapping_ref']))
                $cfg->set(SyncConfiguration::CFG_WRAPPING_COST_REF, $data["wrapping_ref"] ?? 'wrapping');

            // Les modes de règlements ...
            // On va préablement récupérer le mapping existant
            $mapReg = $cfg->get(SyncConfiguration::CFG_ORDER_PAY_MAPPAGE) ?? [ "*" => "" ];

            // Puis, nous allons créer une option pour chaque mode de règlement, de type text
            foreach (array_keys($mapReg) as $key) {
                // On encode la clé avec base64 sans = pour éviter les problèmes de clé
                $safe_key = 'map_reg_'. rtrim(base64_encode($key), '=');
                // On récupère la valeur dans le formulaire
                if (isset($data[$safe_key]))
                {
                    // On met à jour la valeur
                    $mapReg[$key] = $data[$safe_key];                    
                }
            }

            // Les cartes prépayées ?
            if ($this->module->getBridge()->hasSupportFor(SyncConfiguration::CFG_SUPPORT_PREPAID_CARDS))
            {
                if (isset($data['use_prepaid_cards']))
                    $cfg->set(SyncConfiguration::CFG_SUPPORT_PREPAID_CARDS, (bool) ($data["use_prepaid_cards"] ?? false));
            }

            // On enregistre
            $cfg->set(SyncConfiguration::CFG_ORDER_PAY_MAPPAGE, $mapReg);

            // Les bons ...
            $optVouchers = $cfg->get(SyncConfiguration::CFG_VOUCHER_IMPORT_TYPES);
            // Filtrer les données pour ne garder que les champs autorisés
            foreach (array_keys($this->module->getCfg()->describeVoucherConfiguration()) as $key) {
                if (isset($data['import_voucher_'.$key])) {
                    $value = $data['import_voucher_'.$key];
                    if (!is_bool($value))
                    {
                        $this->addFlash('error', "La valeur de 'import_voucher_$key' doit être vrai ou faux.");
                        return $form;
                    }
                    $optVouchers[$key] = $value;
                }                
            }
            $cfg->set(SyncConfiguration::CFG_VOUCHER_IMPORT_TYPES, $optVouchers);


            $this->addFlash('success', 'Configuration sauvegardée !');
        }

        return $form;
    }    

    /**
     * Gère la réinitialisation du jumelage.
     * Renvoi null si pas de confirmation demandée, sinon génère une page de confirmation
     */
    private function handleResetBinding(Request $request)
    {
        if (isset($_REQUEST["reset-binding"]) && $_REQUEST["reset-binding"])
        {
            $form = $this->createForm(ConfirmDialogType::class, [
                'title' => 'Déconnexion du module',
                'txt' => '<p>Êtes-vous sûr de vouloir déconnecter le module de votre backoffice ?</p><p>Pour reprendre la synchronisation des données, il sera nécessaire de procéder à une nouveau jumelage.</p>',
                'yes' => 'Oui, déconnecter',
                'no' => 'Annuler',
                'cancel' => $this->generateUrl($this->module->name . '_configuration'),
            ]);
            $form->handleRequest($request);

            if($form->isSubmitted() && $form->isValid())
            { 
                // Oui, on réinitialise le jumelage en supprimant la configuration de login
                $this->module->getCfg()->set(SyncConfiguration::CFG_LOGIN, null);           
                $this->addFlash('success', 'Jumelage déconnecté !');
                return null;
            }
              
            return $this->render('@Modules/' . $this->module->name . '/views/templates/admin/confirm_dialog.html.twig', $this->addDefaultVars([
                'form' => $form->createView()
            ]));
        }

        return null;
    }

    /**
     * Rendu de la page de configuration des options générales
     */
    public function informationAction(Request $request) 
    {           
        // On vérifie s'il faut réinitialiser le jumelage
        if (isset($_REQUEST["reset-binding"]) && $_REQUEST["reset-binding"])
            if ($p = $this->handleResetBinding($request))
                return $p;

        // Vérifions que le jumelage est déjà fait !
        $bindPage = $this->checkLogin(); if ($bindPage) return $bindPage;       

        // On vérifie s'il faut programmer une resynchronisation totale
        if (isset($_REQUEST["reset-resync"]) && $_REQUEST["reset-resync"])
        {
            // On instancie un syncer
            $syncer = new Syncer($this->module);

            // Demande de resynchronisation totale pour la prochaine fois
            $syncer->bridge->resetSyncPosition();
        }
      

        $form = $this->informationForm($request);
              
        return $this->render('@Modules/' . $this->module->name . '/views/templates/admin/standard.html.twig', $this->addDefaultVars([
            'form' => $form->createView()
        ]));
    }

    /**
     * Rendu de la page de configuration des exportations de commandes
     */
    public function commandeAction(Request $request) {
        // Vérifions que le jumelage est déjà fait !
        $bindPage = $this->checkLogin(); if ($bindPage) return $bindPage;
  
        $form = $this->orderForm($request);

        return $this->render('@Modules/' . $this->module->name . '/views/templates/admin/standard.html.twig', $this->addDefaultVars([
            'form' => $form->createView()
        ]));
    }

    /**
     * Rendu de la page de configuration des stocks
     */
    public function stocksAction(Request $request) {
        // Vérifions que le jumelage est déjà fait !
        $bindPage = $this->checkLogin(); if ($bindPage) return $bindPage;

        $fixStock = false;
        $cfg =  $this->module->getCfg();
        $form = $this->createForm(StocksType::class);

        $form->handleRequest($request);

        if($form->isSubmitted() && $form->isValid()) {
            try
            {
                $data = $form->getData();

                // Option du stock sécurisé
                // On vérifie que le stock sécurisé est un entier, autrement erreur                        
                if (isset($data['secured_stock']))
                {
                    if (!is_numeric($data["secured_stock"]) || $data["secured_stock"] < 0)
                    {
                        $this->addFlash('error', "Le stock sécurisé doit être un nombre entier positif.");
                        return $form;
                    }
                    $cfg->set(SyncConfiguration::CFG_SECURED_STOCK, $data["secured_stock"]);
                }

                // L'option de visibilité des produits ...
                if (isset($data['hide_stock_method']))
                {
                    $fixStock = $cfg->get(SyncConfiguration::CFG_NO_STOCK_PRODUCT_VISIBILITY) !== $data["hide_stock_method"];
                    $cfg->set(SyncConfiguration::CFG_NO_STOCK_PRODUCT_VISIBILITY, $data["hide_stock_method"]);
                }

                if (isset($data['stock_stores']))
                {
                    $store_stocks = [];
                    foreach ($data['stock_stores'] as $storeId)
                        $store_stocks[$storeId] = true;

                    $cfg->set(SyncConfiguration::CFG_ONLINE_STOCK_STORES, $store_stocks);
                }                

                // Les options qui sont rangées dans la configuration du module
                /*foreach (['use_availabilities', 'availability_trigger'] as $key)
                    if (isset($data[$key]))
                        \Configuration::updateValue($this->module->BridgePrefix . "prestaconnector_".$key, $data[$key]);*/

                // Les autres données de configuration
                $this->module->getBridge()->hookSaveAdminForm($form);                

                $form = $this->createForm(StocksType::class);

                $this->addFlash('success', 'Configuration sauvegardée !');

                if ($fixStock)
                    $this->fixNoStockVisibilityAction();
            }
            catch (\Exception $e)
            {
                $this->addFlash('error', "Erreur lors de la sauvegarde de la configuration : " . $e->getMessage());                
            }
        }
    
        return $this->render('@Modules/' . $this->module->name . '/views/templates/admin/standard.html.twig', $this->addDefaultVars([
            'form' => $form->createView()
        ]));
    }

    /**
     * Rendu de la page de configuration du catalogue
     */
    public function catalogueAction(Request $request) {
        // Vérifions que le jumelage est déjà fait !
        $bindPage = $this->checkLogin(); if ($bindPage) return $bindPage;

        $cfg =  $this->module->getCfg();
        $form = $this->createForm(CatalogType::class);

        $form->handleRequest($request);

        if($form->isSubmitted() && $form->isValid()) {
            try
            {
                $data = $form->getData();

                // Les options qui sont rangées dans la configuration du module
                foreach (['use_mpm', 'use_availabilities', 'availability_trigger'] as $key)
                    if (isset($data[$key]))
                        \Configuration::updateValue($this->module->BridgePrefix . "prestaconnector_".$key, $data[$key]);

                // Récupérer uniquement les champs correspondant à $cfgOptions
                $cfgOptions = $cfg->get(SyncConfiguration::CFG_DATA_SYNC);

                // Filtrer les données pour ne garder que les champs autorisés
                foreach (array_keys($this->module->getCfg()->describeSyncConfiguration()) as $key) {
                    if (isset($data[$key])) {
                        $value = $data[$key];
                        if (!is_bool($value))
                        {
                            $this->addFlash('error', "La valeur de '$key' doit être vrai ou faux.");
                            return $form;
                        }
                        $cfgOptions[$key] = $value;
                    }                
                }

                // Sauvegarder uniquement les données filtrées dans la configuration
                $cfg->set(SyncConfiguration::CFG_DATA_SYNC, $cfgOptions);

                /**
                 * Mapping des attributs de produits
                 */
                $productMapping = [
                    SyncConfiguration::PRODUCT_ATTR_PRODUCT => [],
                    SyncConfiguration::PRODUCT_ATTR_VARIANT => [],
                    SyncConfiguration::PRODUCT_ATTR_FEATURE => []
                ];
                //$cfg->get(SyncConfiguration::CFG_PR_ATTRIBUTES_MAPPING);
                foreach (array_merge($cfg->get(SyncConfiguration::CFG_DYN_ATTR), $cfg->get(SyncConfiguration::CFG_DYN_CAT)) as $k => $name)
                {
                    $base_safe_key = trim(base64_encode($k), '=');

                    if (isset($data['PRODUCT_ATTR_PRODUCT_'.$base_safe_key]))
                        $productMapping[SyncConfiguration::PRODUCT_ATTR_PRODUCT][$k] = $data['PRODUCT_ATTR_PRODUCT_'.$base_safe_key];

                    if (isset($data['PRODUCT_ATTR_VARIANT_'.$base_safe_key]))
                        $productMapping[SyncConfiguration::PRODUCT_ATTR_VARIANT][$k] = $data['PRODUCT_ATTR_VARIANT_'.$base_safe_key];

                    if (isset($data['PRODUCT_ATTR_FEATURE_'.$base_safe_key]))
                        $productMapping[SyncConfiguration::PRODUCT_ATTR_FEATURE][$k] = $data['PRODUCT_ATTR_FEATURE_'.$base_safe_key];
                }

                // Mappage des couleurs : une seule source (attribut OU catégorie
                // back-office) vers un groupe d'attributs Prestashop.
                $src = $data['color_mapping']['source'] ?? '';
                $dst = (int) ($data['color_mapping']['destination'] ?? 0);
                $map_colors = ($src !== '' && $dst > 0) ? [$src => $dst] : [];
                $cfg->set(SyncConfiguration::CFG_MAP_COLOR_ATTRS, $map_colors);

                // Mappage des propriétés personnalisables (Polaris 15+) → caractéristiques
                $customPropsMap = [];
                foreach ($cfg->get(SyncConfiguration::CFG_CUSTOM_PROPS) ?? [] as $code => $name)
                {
                    $safe = trim(base64_encode($code), '=');
                    if (isset($data['PRODUCT_PROP_FEATURE_'.$safe]))
                        $customPropsMap[$code] = $data['PRODUCT_PROP_FEATURE_'.$safe];
                }
                $cfg->set(SyncConfiguration::CFG_MAP_CUSTOM_PROPS, $customPropsMap);

                // Les attributs non mappés sont forcément discriminants !
                // sauf si ce sont des propriétés
                $dynProps = $cfg->get(SyncConfiguration::CFG_DYN_PROP) ?? [];
                foreach (array_keys($cfg->get(SyncConfiguration::CFG_DYN_ATTR)) as $k)
                {
                    if (isset($dynProps[$k]) && $dynProps[$k])
                        continue;
                    if (!isset($productMapping[SyncConfiguration::PRODUCT_ATTR_VARIANT][$k]) || !$productMapping[SyncConfiguration::PRODUCT_ATTR_VARIANT][$k])
                    {
                        $productMapping[SyncConfiguration::PRODUCT_ATTR_PRODUCT][$k] = true;
                        $data['PRODUCT_ATTR_PRODUCT_'.trim(base64_encode($k), '=')] = true;
                    }
                }
                $cfg->set(SyncConfiguration::CFG_PR_ATTRIBUTES_MAPPING, $productMapping);
                $cfg->set(SyncConfiguration::CFG_ATTR_AUTO_CLEAN, $data['ATTR_AUTO_CLEAN']);
                $cfg->set(SyncConfiguration::CFG_PRODUCT_NAME_TEMPLATE, $data[SyncConfiguration::CFG_PRODUCT_NAME_TEMPLATE] ?? '');                

                // Catégories par défaut
                // On enregistre
                $cats = $data[SyncConfiguration::CFG_DEFAULT_ROOT_CATEGORIES] ?? [];
                if (!is_array($cats))
                    $cats = [$cats];

                $cfg->set(SyncConfiguration::CFG_DEFAULT_ROOT_CATEGORIES, $cats);

                if (isset($data['WITH_STOCK_ONLY']))
                    $cfg->set(SyncConfiguration::CFG_PRODUCT_IMPORT_ONLY_WITH_STOCK, $data['WITH_STOCK_ONLY'] ? true : false);

                // Les autres données de configuration
                $this->module->getBridge()->hookSaveAdminForm($form);
                
                $form = $this->createForm(CatalogType::class);

                $this->addFlash('success', 'Configuration sauvegardée !');
            }
            catch (\Exception $e)
            {
                $this->addFlash('error', "Erreur lors de la sauvegarde de la configuration : " . $e->getMessage());                
            }
        }
    
        return $this->render('@Modules/' . $this->module->name . '/views/templates/admin/standard.html.twig', $this->addDefaultVars([
            'form' => $form->createView()
        ]));
    }
    
    /**
     * Rendu de la page de consultation d'audit
     */
    public function auditAction(Request $request) {
        
        $content = null;
        if ($request->get('file'))
        {
            $date = $request->get('file');
            $file = _PS_ROOT_DIR_ . '/var/audits/'.$this->module->name.'/'.'audit-'.$date.'.log';
            if (!file_exists($file))
            {
                $this->addFlash('error', "Le fichier d'audit n'existe pas.");
                return $this->redirectToRoute($this->module->name . '_audit');
            }

            if ($request->get('download') == 1)
            {                
                // On retourne le contenu du fichier avec Symfony
                $response = new \Symfony\Component\HttpFoundation\Response();
                $response->headers->set('Content-Type', 'text/plain');
                $response->headers->set('Content-Disposition', 'attachment; filename="'.'audit-'.$date.'.log'.'"');
                $response->setContent(file_get_contents($file));

                return $response;
            }
            else
            {
                // Affichage simple
                $content = file_get_contents($file);
            }
        }

        $audits = [];

        // Si on a pas de contenu, c'est qu'on veut la liste des audits
        if (!isset($content))
        {
            // On cherche les fichiers audit disponible dans le répertoire ROOT/var/audits/prestaconnector
            $files = glob(_PS_ROOT_DIR_ . '/var/audits/'.$this->module->name.'/audit-*.log');

            rsort($files);
            foreach ($files as $file)
            {
                $file = basename($file);
                // le nom du fichier est du style : audit-YYYYMMDD.log (ex. : audit-20210601.log)
                // Je cherche à extraire la date
                $date = substr($file, 6, 4) . '-' . substr($file, 10, 2) . '-' . substr($file, 12, 2);            
                $ts = strtotime($date);

                // On traduit la date en "mois année" (i.e. : Novembre 2021) sans strftime qui est déprécié
                $month = date('F Y', $ts);

                // Puis on traduit la date au format francais : 01/01/2021
                $date = date('d/m/Y', $ts);

                if (!isset($audits[$month]))
                    $audits[$month] = [];

                $audits[$month][] = [
                                'key' => substr($file, 6, 4) . substr($file, 10, 2) . substr($file, 12, 2),
                                'title' => $date,
                            ];
            }
        }
    
        return $this->render('@Modules/' . $this->module->name . '/views/templates/admin/audit.html.twig', $this->addDefaultVars([
            'audits' => $audits,
            'content_file' => $request->get('file'),
            'content_file' => $request->get('file'),
            'content' => $content,
        ]));
    }     

    /**
     * Mise à jour d'un mapping produit
     */
    public function updateProductsMappingAction(Request $request): JsonResponse
    {
        $idCombination = (int) $request->request->get('id_combination');
        $supplierReference = pSQL($request->request->get('supplier_reference'));

        if (!$idCombination || empty($supplierReference)) {
            return new JsonResponse(['success' => false, 'message' => 'Invalid data'], 400);
        }

        \Db::getInstance()->execute(
            'UPDATE '._DB_PREFIX_.'product_supplier 
             SET product_supplier_reference = "'.pSQL($supplierReference).'" 
             WHERE id_product_attribute = '.(int) $idCombination
        );

        return new JsonResponse(['success' => true, 'message' => 'Updated successfully']);
    }

    /**
     * Fonction qui corrige la visibilité des produits sans stock aux produits
     * actuels en les regardant tous
     */
    private function fixNoStockVisibilityAction()
    {
        $cfg =  $this->module->getCfg();
        $hide_stock_method = $cfg->get(SyncConfiguration::CFG_NO_STOCK_PRODUCT_VISIBILITY);
        if (!$hide_stock_method)
            return; // Rien à faire

        // On va regarder corriger avec une simple requête SQL update
        $db = \Db::getInstance();

        // On montre tous les produits qui ont du stock (calculé depuis une somme de ps_stock_available)
        foreach (['product', 'product_shop'] as $table)
        {
            $updateSql = sprintf(@'UPDATE '._DB_PREFIX_.$table.' p
                                    SET p.visibility = \'%s\'
                                    WHERE p.id_product IN (
                                        SELECT sa.id_product FROM '._DB_PREFIX_.'stock_available sa 
                                        WHERE sa.quantity > 0 
                                        GROUP BY sa.id_product)', 'both');
            $result = $db->execute($updateSql);
            if ($result === false)
                $this->addFlash('error', "Erreur lors de la correction des produits avec stock : " . $db->getMsgError());
                    
            // On cache tous les produits qui n'ont pas de stock (calculé depuis une somme de ps_stock_available)
            $updateSql = sprintf(@'UPDATE '._DB_PREFIX_.'product_shop p
                                    SET p.visibility = \'%s\'
                                    WHERE p.id_product NOT IN (
                                        SELECT sa.id_product FROM '._DB_PREFIX_.'stock_available sa 
                                        WHERE sa.quantity > 0 
                                        GROUP BY sa.id_product)', 
                                        $hide_stock_method);
            $result = $db->execute($updateSql);
            if ($result === false)
                $this->addFlash('error', "Erreur lors de la correction des produits avec stock : " . $db->getMsgError());             
        }
    }

    /**
     * Rendu menu des options avancées
     */
    public function advancedMenuAction()
    {
        return $this->render('@Modules/' . $this->module->name . '/views/templates/admin/advanced_options_menu.html.twig', $this->addDefaultVars([]));
    }
}