<?php
namespace PrestaShop\Module\PolarisPrestaConnector;

use Exception;
use ObjectModel;

/**
 * Classe d'aide à la journalisation des modifications
 */
class AuditHelper
{
    /**
     * Nom de l'objet
     */
    private ObjectModel $object;

    /**
     * Indique si l'objet est nouveau
     * 
     * @var bool
     */
    public bool $isNew = false;

    /**
     * Valeur avant modification
     */
    private array|null $begin = null;

    /**
     * Valeur après modification
     */
    private array|null $end = null;

    /**
     * Tableau associatif id_lang => iso_code
     */
    private static array $langs;

    /**
     * Constructeur
     * 
     * @param object $object Objet à journaliser
     */
    public function __construct(ObjectModel $object)
    {
        // Calcul du tableau associatif des langues s'il n'existe pas déjà
        if (!isset(self::$langs))
        {
            self::$langs = [];
            foreach (\Language::getLanguages(false) as $lang)
                self::$langs[$lang["id_lang"]] = $lang["iso_code"];
        }

        $this->object = $object;
        $this->isNew = !$object->id;
        if (!$this->isNew)
            $this->snapshot(true);
    }

    /**
     * Effectue un snapshot de l'objet et renvoi un tableau des propriétés
     */
    private function snapshot(bool $begin = false) : array
    {        
        $snapshot = [
            "core" => $this->object->getFields(), 
            // "shop" => $this->object->getFieldsShop(), // a priori, ça ne sert à rien de journaliser les champs shop
            "lang" => $this->object->getFieldsLang(),
        ]; 
        
        // On stocke le snapshot
        if ($begin) {
            $this->begin = $snapshot;
        } else {
            $this->end = $snapshot;
        }

        return $snapshot;
    }

    /**
     * Calcule la différence entre le begin et le end
     */
    public function diff() : array
    {        
        // snapshot de fin
        $this->snapshot(false);

        // on produit un tableau associatif dont la clé est le nom de la propriété
        // seulement si la valeur a changé
        $diff = [];

        // la base
        foreach ($this->end["core"] as $k => $ev)
        {
            $sv = isset($this->begin["core"][$k]) ? $this->begin["core"][$k] : null;
            if ($ev != $sv) {
                $diff[$k] = ["s" => $sv, "e" => $ev];
            }
        }

        // le shop
        if (isset($this->end["shop"])) {
            foreach ($this->end["shop"] as $k => $ev)
            {
                $sv = isset($this->begin["shop"][$k]) ? $this->begin["shop"][$k] : null;
                if ($ev != $sv) {
                    $diff["shop_".$k] = ["s" => $sv, "e" => $ev];
                }
            }
        }

        // les langues
        foreach ($this->end["lang"] as $lang => $fields)
        {
            // détermination de la langue en fonction de l'index dans $lang
            $lang_str = self::$langs[$lang];
            foreach ($fields as $k => $ev)
            {
                $sv = isset($this->begin["lang"][$lang][$k]) ? $this->begin["lang"][$lang][$k] : null;
                if ($ev != $sv) {
                    $diff[$lang_str."_".$k] = ["s" => $sv, "e" => $ev];
                }
            }
        }

        return $diff;
    }

    /**
     * Obtient le message d'audit correspondant à la mutation de l'objet
     * 
     */
    public function getAuditMessage($diff) : string|null
    {
        if (!$diff)
            $diff = $this->diff();
        if (!empty($diff))
        {
            // Calcul du messagei
            if ($this->isNew)            
                $msg = "Création de " . get_class($this->object) . "#" . $this->object->id . " : ";                
            else
                $msg = "Modification de " . get_class($this->object) . "#" . $this->object->id . " : ";

            $msg .= implode("; ", array_map(function($k, $v) {
                if ($k != "date_add" && $k != "date_upd" && !is_array($v["s"]) && !is_array($v["e"]))
                    return $k . " : " . ($v["s"] ?? '') . " → " . ($v["e"] ?? '');
                return '';
            }, array_keys($diff), $diff));
            
            return $msg;
        }
        else
            return null;
    }

    /**
     * Sauve les modifications faîtes sur l'objet en s'assurant d'avoir sauvé également 
     * l'audit concernant ces modifications s'il y en a
     * 
     * @param Syncer $syncer Syncer à utiliser pour la sauvegarde
     */
    public function save(Syncer $syncer)
    {
        // on dresse l'audit
        $diff = $this->diff();        
        if (!$this->isNew && !$diff)
            return; // Rien à sauver !

        // on sauvegarde l'objet
        $this->object->save();

        $syncer->audit($this->getAuditMessage($diff));

        // au cas où le snapshot serait réutilisé, on reset les valeurs de départ
        $this->begin = $this->end;
        $this->isNew = false;
    }
}