<?php
namespace PrestaShop\Module\LCVPrestaConnector;

/**
 * Gestionnaire de features
 * 
 */
class FeaturesManager
{    
    /**
     * Les features sont chargées une seule fois en statique
     * 
     * @var array<int, \Feature>|null tableau des id de features
     */
    private static ?array $features = null;

    /**
     * Valeurs existantes des features
     * feature_id => [ value => value_id ]
     * 
     * @var array<int, array<string, int>> tableau associatif de tableau associatif de valeurs de features
     */
    private static array $featuresValues = [];

    /**
     * Caractéristiques des produits
     * product_id => [ feature_id => feature_value_id ]
     * 
     * @var array<int, array<int, int>> tableau associatif de tableau associatif de valeurs de features
     */
    private array $productsFeatures = [];

    /**
     * Récupère les features existantes
     * 
     * @return array<int, int> tableau des id de features
     */
    public static function & getExistingFeatures() : array
    {
        // Chargement des id de features
        if (is_null(self::$features)) {
            $db = \Db::getInstance();

            // Chargement des id caractéristiques existantes sur le système            
            $res = $db->executeS('SELECT `id_feature` FROM `'._DB_PREFIX_.'feature`');
            if ($res === FALSE)
                throw new \Exception('Erreur lors de la récupération des caractéristiques de produits : '.$db->getMsgError());

            self::$features = [];
            foreach ($res as $row)
                self::$features[(int) $row['id_feature']] = (int) $row['id_feature'];
        }

        return self::$features;
    }

    /**
     * S'assure que les valeurs de caractéristique sont bien chargées pour une feature en particulier
     * 
     */
    private static function loadFeaturesValues(int $id_feature)
    {        
        if (!array_key_exists($id_feature, self::$featuresValues))
        {
            $db = \Db::getInstance();
            // Langue par défaut
            $id_lang = \Configuration::get('PS_LANG_DEFAULT');

            // Chargement des id de features values pour les produits            
            $res = $db->executeS('SELECT f.`id_feature_value`, l.`value` FROM `'._DB_PREFIX_.'feature_value_lang` AS l LEFT JOIN `'._DB_PREFIX_.'feature_value` AS f USING (`id_feature_value`) WHERE l.`id_lang` = ' . $id_lang . ' AND f.`id_feature` = '.$id_feature);
            if ($res === FALSE)
                throw new \Exception('Erreur lors de la récupération des valeurs de caractéristiques possibles de produits : '.$db->getMsgError());

            self::$featuresValues[(int) $id_feature] = [];
            foreach ($res as $row)
                self::$featuresValues[(int) $id_feature][$row['value']] = (int) $row['id_feature_value'];
        }
    }

    /** 
     * Instancie un nouveau gestionnaire de features
     * 
     * @param array<int> $productsId produits gérés, ou aucun pour tout charger
    */
    public function __construct(?array $productsId = null)
    {
        $db = \Db::getInstance();

        // Chargement des id de features values pour les produits
        $this->productsFeatures = [];

        $sql = 'SELECT `id_product`, `id_feature`, `id_feature_value` FROM `'._DB_PREFIX_.'feature_product`';
        if (isset ($productsId) && !empty($productsId))
            $sql .= ' WHERE `id_product` IN ('.implode(',', $productsId).')';

        $res = $db->executeS($sql);
        if ($res === FALSE)
            throw new \Exception('Erreur lors de la récupération des caractéristiques des produits : '.$db->getMsgError());
        
        foreach ($res as $row) {
            if (!isset($this->productsFeatures[$row['id_product']]))
                $this->productsFeatures[$row['id_product']] = [];
            $this->productsFeatures[(int) $row['id_product']][(int) $row['id_feature']] = (int) $row['id_feature_value'];
        }
    }

    /**
     * Change le produit pour que les features communiquées dans data soit positionnées correctement sur le produit.
     * 
     * @param Syncer $syncer
     * @param int $id_product
     * @param array<int, string> $data tableau associatif de id_feature => id_feature_value
     */
    public function setProductFeature(Syncer $syncer, int $id_product, array $data)
    {
        // Pour chaque feature, on vérifie si elle existe
        foreach ($data as $id_feature => $feature_value)
        {
            self::loadFeaturesValues($id_feature);
            if (!array_key_exists($feature_value, self::$featuresValues[$id_feature]))
            {
                // La valeur n'existe pas encore, il faut la créée                
                $new_fv = new \FeatureValue();
                $new_fv->id_feature = (int) $id_feature;                
                $new_fv->value = array_fill_keys(\Language::getIDs(false), $feature_value);
                $new_fv->custom = false;
                $new_fv->add();

                $syncer->audit('Création de la valeur de caractéristique `'.$feature_value.'` pour la caractéristique #'.$id_feature);

                // Valeur créée, on l'ajoute à notre cache
                self::$featuresValues[$id_feature][$feature_value] = (int) $new_fv->id;
            }

            // On vérifie si la feature est déjà positionnée pour ce produit
            $id_feature_value = self::$featuresValues[$id_feature][$feature_value];

            if (
                !isset($this->productsFeatures[$id_product]) || 
                !isset($this->productsFeatures[$id_product][$id_feature]) || 
                $this->productsFeatures[$id_product][$id_feature] != $id_feature_value)
            {                
                // La feature n'est pas positionnée, on la positionne
                // On insère ou remplace l'ancienne valeur
                $sql = 'REPLACE INTO `'._DB_PREFIX_.'feature_product` (`id_feature`, `id_product`, `id_feature_value`) VALUES ('.$id_feature.', '.$id_product.', '.$id_feature_value.')';
                $res = \Db::getInstance()->execute($sql);
                if ($res === FALSE)
                    throw new \Exception('Erreur lors de la mise à jour des caractéristiques des produits : '.\Db::getInstance()->getMsgError());                            

                $syncer->audit('Changement caractéristique #'.$id_feature.' pour le produit #'.$id_product." : ".$feature_value);

                // On update le cache
                $this->productsFeatures[$id_product][$id_feature] = $id_feature_value;
            }
        }
    }

    /**
     * Retire une caractéristique d'un produit.
     * N'agit (et n'audite) que si la caractéristique est réellement positionnée sur le produit.
     *
     * @param Syncer $syncer
     * @param int $id_product
     * @param int $id_feature
     */
    public function removeProductFeature(Syncer $syncer, int $id_product, int $id_feature)
    {
        // Rien à faire si la caractéristique n'est pas positionnée sur ce produit
        if (!isset($this->productsFeatures[$id_product][$id_feature]))
            return;

        $sql = 'DELETE FROM `'._DB_PREFIX_.'feature_product` WHERE `id_product` = '.$id_product.' AND `id_feature` = '.$id_feature;
        $res = \Db::getInstance()->execute($sql);
        if ($res === FALSE)
            throw new \Exception('Erreur lors de la suppression d\'une caractéristique de produit : '.\Db::getInstance()->getMsgError());

        $syncer->audit('Suppression de la caractéristique #'.$id_feature.' du produit #'.$id_product);

        // On update le cache
        unset($this->productsFeatures[$id_product][$id_feature]);
    }
}