<?php
namespace PrestaShop\Module\LCVPrestaConnector;

/**
 * Gestionnaire de référence fournisseur
 * 
 */
class SupplierRefManager
{
    /**
     * Identifiant du fournisseur
     */
    private string $supplierId;

    /**
     * Cache des références fournisseur pour les produits
     * 
     * @var array<string, int> tableau associatif de références fournisseur => id de produit
     */
    private array $productsCache = [];    

    /**
     * Cache des références fournisseur pour les déclinaisons
     * 
     * @var array<string, array<array<int>>> tableau associatif de références fournisseur => { 0 => id_product, 1 => id_product_attribute }
     */
    private array $combinationsCache = [];

    /** 
     * Instancie un nouveau gestionnaire de référence
     * 
     * @param int $supplierId identifiant du fournisseur
     * @param array<int> $productsId produits gérés, ou aucun pour tout charger
    */
    public function __construct($supplierId, ?array $productsId = null)    
    {
        $this->supplierId = $supplierId;

        $db = \Db::getInstance();

        $this->productsCache = [];
        $this->combinationsCache = []; 

        // Chargement des références fournisseur depuis le fichier product_supplier
        $sql = 'SELECT `id_product`, `id_product_attribute`, `product_supplier_reference` FROM `'._DB_PREFIX_.'product_supplier` WHERE `id_supplier` = '.$supplierId;
        if (isset ($productsId) && !empty($productsId))
            $sql .= ' AND `id_product` IN ('.implode(',', $productsId).')';
        $res = $db->executeS($sql);
        if ($res === FALSE)
            throw new \Exception('Erreur lors de la récupération des références fournisseur : '.$db->getMsgError());

        foreach ($res as $row)
        {
            $product_reference = $row['product_supplier_reference'];
            $id_product = (int) $row['id_product'];
            $id_product_attribute = (int) $row['id_product_attribute'];

            if ($id_product_attribute)
            {
                if (!isset($this->combinationsCache[$product_reference]))
                    $this->combinationsCache[$product_reference] = [];
                $this->combinationsCache[$product_reference][] = [ $id_product, (int) $row['id_product_attribute'] ];
            }

            $this->productsCache[$product_reference] = $id_product;
        }
    }

    /**
     * Renvoi le nombre de références produit dans le cache
     */
    public function getNbProducts() : int
    {
        return count($this->productsCache);
    }

    /**
     * Traduit une référence fournisseur en identifiant de produit
     * 
     * @param string $ref référence fournisseur à traduire
     * @return int|null identifiant de produit, ou null si non trouvé
     */
    public function translateProductRef(string $ref) : ?int
    {
        return $this->productsCache[$ref] ?? null;
    }

    /**
     * Retrouve la référence produit backoffice à partir de l'identifiant de produit Prestashop
     */
    public function translateProductId(int $id_product) : ?string
    {
        foreach ($this->productsCache as $ref => $id)
        {
            if ($id == $id_product)
                return $ref;
        }
        
        return null;
    }

    /**
     * Traduit une référence fournisseur en identifiant de combinaison
     * 
     * @param string $ref référence fournisseur à traduire
     * @return int|null identifiant de combinaison, ou null si non trouvé
     */
    public function translateCombinationRef(string $ref) : ?int
    {
        $firstRef = $this->combinationsCache[$ref] ?? null;
        if ($firstRef && count($firstRef) > 0)
            return $firstRef[0][1];
        return null;
    }

    /**
     * Traduit une référence fournisseur en identifiant de combinaison
     * 
     * @param string $ref référence fournisseur à traduire
     * @return array<array<int>>|null tableau d'identifiants de produit (0) et de combinaison (1), ou null si non trouvé
     */
    public function translateCombinationRefs(string $ref) : ?array
    {
        return $this->combinationsCache[$ref] ?? null;
    }

    /**
     * Récupère toutes les références qui correspondent à un masque (par exemple : "XX*XX*")
     */
    public function getReferencesFromWildcard(string $ref) : array
    {
        // On parcours le cache des références et on récupère celles qui correspondent au masque
        $matches = [];
        foreach ($this->combinationsCache as $c_ref => $id_product)
        {
            if (fnmatch($ref, $c_ref))
            {
                $matches[] = $c_ref;
            }
        }

        return $matches;
    }

    /**
     * Traduit plusieurs références fournisseurs en identifiant de combinaison
     * 
     * @param array<string> $refs références fournisseur à traduire
     * @return array<array<int>>|null tableau d'identifiants de produit (0) et de combinaison (1), ou null si non trouvé
     */
    public function translateCombinationOneRef(array $refs) : ?array
    {
        foreach ($refs as $ref)
        {
            $translated = $this->combinationsCache[$ref] ?? null;
            if ($translated)
                return $translated;
        }

        return null;
    }
    
    /**
     * Traduit une référence fournisseur de déclinaison en identifiant de produit
     * 
     * @param string $ref référence fournisseur à traduire
     * @return array<int>|null identifiant de produit, ou null si non trouvé
     */
    public function translateProductAttributeRefs(string $ref) : ?array
    {
        $id_products = null;
        if (isset($this->combinationsCache[$ref]) && count($this->combinationsCache[$ref]) > 0)
        {
            $id_products = [];
            foreach ($this->combinationsCache[$ref] as $lnk)
                $id_products[] = $lnk[0];            
        }

        return $id_products;
    } 

    /**
     * Enregistre une référence fournisseur pour un produit
     * 
     * @param string $ref référence fournisseur à enregistrer
     * @param int $id_product identifiant du produit
     */
    public function registerProductRef(string $ref, int $id_product)
    {
        // Obtenir la devise par défaut du système
        $currencyId = \Configuration::get('PS_CURRENCY_DEFAULT');

        $db = \Db::getInstance();
        
        // On remplace les données dans la base de données, si elles existent déjà, ou on les insère
        // (REPLACE)
        $sql = 'REPLACE INTO `'._DB_PREFIX_.'product_supplier` (`id_product`, `id_product_attribute`, `id_supplier`, `id_currency`, `product_supplier_reference`) VALUES ('.$id_product.', 0, '.$this->supplierId.',  '.$currencyId.', 
                    \''.$db->_escape($ref).'\')';
        if ($db->execute($sql) === false)
            throw new \Exception('Erreur lors de l\'enregistrement de la référence fournisseur '.$ref.' : '.$db->getMsgError());

        $this->productsCache[$ref] = $id_product;
    }

    /**
     * Enregistre une référence fournisseur pour un produit
     * 
     * @param string $ref référence fournisseur à enregistrer
     * @param int $id_product identifiant du produit
     * @param int $id_product_attribute identifiant de la déclinaison
     */
    public function registerCombinationRef(string $ref, int $id_product, int $id_product_attribute)
    {
        // Obtenir la devise par défaut du système
        $currencyId = \Configuration::get('PS_CURRENCY_DEFAULT');

        $db = \Db::getInstance();
        
        // On remplace les données dans la base de données, si elles existent déjà, ou on les insère
        // (REPLACE)
        $sql = 'REPLACE INTO `'._DB_PREFIX_.'product_supplier` (`id_product`, `id_product_attribute`, `id_supplier`, `id_currency`, `product_supplier_reference`) VALUES ('.$id_product.', '. $id_product_attribute .', '.$this->supplierId.',  '.$currencyId.', 
                    \''.$db->_escape($ref).'\')';
        if ($db->execute($sql) === false)
            throw new \Exception('Erreur lors de l\'enregistrement de la référence fournisseur '.$ref.' : '.$db->getMsgError());

        $found = false;
        if (isset($this->combinationsCache[$ref]))
        {
            // On regarde si la référence est déjà enregistrée pour le produit            
            foreach ($this->combinationsCache[$ref] as &$lnk)
            {
                if ($lnk[0] == $id_product)
                {
                    $found = true;
                    $lnk[1] = $id_product_attribute;
                }
            }
        }
        
        if (!$found)    // cas simple
            $this->combinationsCache[$ref][] = [ $id_product, $id_product_attribute ];
    }

    /**
     * Efface une référence fournisseur produit car elle n'est plus valide
     * 
     */
    public function cleanProductRef(string $ref)
    {
        $db = \Db::getInstance();
        
        // On efface les données dans la base de données
        $sql = 'DELETE FROM `'._DB_PREFIX_.'product_supplier` WHERE `id_supplier` = '.$this->supplierId.' AND `id_product_attribute` = 0 AND `product_supplier_reference` = \''.$db->_escape($ref).'\'';
        if ($db->execute($sql) === false)
            throw new \Exception('Erreur lors de l\'effacement de la référence fournisseur '.$ref.' : '.$db->getMsgError());

        unset($this->productsCache[$ref]);
    }

    /**
     * Efface une référence fournisseur déclinaison car elle n'est plus valide
     * 
     * @param string $ref référence fournisseur à effacer
     * @param int $id_product_attribute identifiant de la déclinaison
     */
    public function cleanCombinationRef(string $ref, int $id_product_attribute)
    {
        $db = \Db::getInstance();
        
        // On efface les données dans la base de données
        $sql = 'DELETE FROM `'._DB_PREFIX_.'product_supplier` WHERE `id_supplier` = '.$this->supplierId.' AND `id_product_attribute` = '.$id_product_attribute . ' AND `product_supplier_reference` = \''.$db->_escape($ref).'\'';
        if ($db->execute($sql) === false)
            throw new \Exception('Erreur lors de l\'effacement de la référence `'.$ref.'` pour la déclinaison #'.$id_product_attribute.' : '.$db->getMsgError());

        // Nettoyage du cache
        if (isset($this->combinationsCache[$ref]))
        {
            $new_links = [];
            foreach ($this->combinationsCache[$ref] as $lnk)
            {
                if ($lnk[1] != $id_product_attribute)
                    $new_links[] = $lnk;
            }
            $this->combinationsCache[$ref] = $new_links;
        }
    }

    /**
     * Efface toutes les références fournisseurs pour un produit donnés
     * 
     * @param int $id_product identifiant du produit
     * @param bool $deleteMain true pour effacer aussi la référence principale du produit, false pour ne pas l'effacer
     */
    public function cleanAllCombinationRefs(int $id_product, bool $deleteMain = false)
    {
        $db = \Db::getInstance();

        // On efface les données dans la base de données
        $sql = 'DELETE FROM `'._DB_PREFIX_.'product_supplier` WHERE `id_supplier` = '.$this->supplierId.' AND `id_product` = '.$id_product;
        if ($deleteMain) 
            $sql .= ' AND `id_product_attribute` != 0';

        if ($db->execute($sql) === false)
            throw new \Exception('Erreur lors de l\'effacement des références fournisseur pour #'.$id_product.' : '.$db->getMsgError());

        // Nettoyage du cache
        foreach ($this->combinationsCache as $ref => $attrs)
        {
            $change = false;
            $new_links = [];
            foreach ($attrs as $lnk)
            {
                if ($lnk[0] != $id_product)
                    $new_links[] = $lnk;
                else
                    $change = true;
            }

            if ($change)
                $this->combinationsCache[$ref] = $new_links;
        }

        if ($deleteMain)
        {
            // On efface aussi la référence principale du produit
            foreach ($this->productsCache as $ref => $id)
            {
                if ($id == $id_product)
                    unset($this->productsCache[$ref]);
            }
        }
    }
}