-- Le fil de notifications, la matrice de reglages, et le droit d'opposition.
--
-- TROIS SUJETS DANS UN SEUL FICHIER, et c'est assume : ils n'ont aucun sens separement.
-- Un fil sans reglages inonde ; des reglages sans opposition font du concierge le maitre
-- d'une boite qui n'est pas la sienne ; une opposition sans fil prive le proprietaire
-- d'une information sur son propre bien. C'est une seule fonction, pas trois.
--
-- CE QUI N'EST PAS ICI : le courriel. Le fil ne demande ni domaine authentifie, ni relais,
-- ni cron. Il part donc d'abord, et il servira de registre a ce qui s'enverra ensuite.

-- ============================================================ LE FIL ==============
--
-- Le destinataire est TOUJOURS un compte, jamais une adresse libre. Un client peut
-- exister sans compte (clients.email le prevoit explicitement), mais lui pousser une
-- information qu'il n'a nulle part ou consulter reintroduirait le probleme meme que le
-- fil resout. Un client sans compte n'est donc pas notifie — et l'ecran doit le dire,
-- plutot que de le taire.
CREATE TABLE notifications (
    id              BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    destinataire_id BIGINT UNSIGNED NOT NULL,
    -- Le vocabulaire est celui de App\Domain\EvenementNotification, et VARCHAR pour la
    -- meme raison que intervention_evenements.type : un evenement s'ajoute au fil des
    -- fonctions, et un ALTER sur une table qui grossit indefiniment se paie. Une valeur
    -- inconnue est affichee brute a la lecture, jamais masquee.
    evenement       VARCHAR(40)     NOT NULL,
    -- ALERTE OU SIMPLE INFORMATION, FIGE A L'EMISSION.
    --
    -- On pourrait le relire depuis le domaine a l'affichage. Ce serait une erreur : le
    -- classement d'un evenement peut changer d'avis (on decidera un jour qu'une
    -- modification acceptee alerte l'intervenant), et un fil qui se relit avec les regles
    -- d'aujourd'hui raconterait que l'utilisateur a ete alerte alors qu'il ne l'a pas ete.
    alerte          TINYINT(1)      NOT NULL DEFAULT 0,
    -- LE SUJET, EN TYPE + IDENTIFIANT plutot qu'une cle etrangere par espece.
    --
    -- Une FK par type (intervention_id, bien_id, client_id, compte_id...) ferait une
    -- colonne de plus a chaque fonction notifiable, toutes nulles sauf une. Le prix a
    -- payer est qu'aucune contrainte ne garantit la cible : c'est pourquoi le titre est
    -- fige ci-dessous, et pourquoi le lien est verifie a l'affichage.
    sujet_type      VARCHAR(20)     NOT NULL,
    sujet_id        BIGINT UNSIGNED NULL,
    -- LE TEXTE EST REDIGE A L'EMISSION, PAS A LA LECTURE.
    --
    -- C'est le point le plus important de cette table. Une notification decrit un MOMENT :
    -- « la date du 12 mars a ete deplacee au 19 ». Le recomposer plus tard depuis l'etat
    -- courant de l'intervention afficherait la verite d'aujourd'hui a la place de celle
    -- qu'on a voulu signaler — et pour les six alertes, dont tout l'objet est de dire
    -- « ce que vous croyez savoir est faux », ce serait exactement l'inverse du but.
    titre           VARCHAR(190)    NOT NULL,
    detail          VARCHAR(255)    NULL,
    -- Ou aller pour agir. NULL quand il n'y a rien a ouvrir.
    lien            VARCHAR(190)    NULL,
    -- L'ENVOI PAR COURRIEL, TRACE ICI ET NULLE PART AILLEURS.
    --
    -- NULL ne veut pas dire « echoue » : il veut dire « pas envoye », ce qui couvre aussi
    -- « pas encore » et « pas prevu ». La distinction se lit avec courriel_erreur, qui
    -- porte la raison quand il y en a une. Sans ces deux colonnes, la question « le client
    -- a-t-il ete prevenu ? » n'a pas de reponse — et c'est la question qui arrive.
    courriel_le     DATETIME        NULL,
    courriel_erreur VARCHAR(190)    NULL,
    cree_le         DATETIME        NOT NULL,
    -- LU PAR PERSONNE, PAS PAR NOTIFICATION : le destinataire etant unique par ligne, la
    -- colonne suffit. Si un jour une notification visait un groupe, il faudrait une table
    -- de lectures — mais dupliquer la ligne par destinataire est plus simple et rend le
    -- « lu » gratuit.
    lu_le           DATETIME        NULL,
    PRIMARY KEY (id),
    -- L'index qui porte les deux seules lectures : le compteur de non-lues, et le fil dans
    -- l'ordre inverse. lu_le au milieu parce que le compteur filtre dessus.
    KEY idx_notif_fil (destinataire_id, lu_le, cree_le),
    -- CASCADE : le fil d'un compte supprime n'a pas d'objet. Le journal d'audit, lui,
    -- conserve la trace des faits — ce sont deux besoins differents.
    CONSTRAINT fk_notif_destinataire FOREIGN KEY (destinataire_id)
        REFERENCES utilisateurs (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ====================================================== LA MATRICE ================
--
-- CETTE TABLE NE PORTE QUE LES ECARTS AU DEFAUT, et c'est la decision structurante.
--
-- Si elle portait la matrice complete, alors le jour ou l'on ajoute un quatorzieme
-- evenement il faudrait une migration qui invente une ligne pour chaque concierge
-- existant. Celui qu'on oublierait — ou celui cree entre deux versions — ne recevrait
-- rien, en silence. Une absence de ligne DOIT signifier « le defaut », jamais « eteint ».
--
-- Consequence heureuse : un evenement nouveau arrive avec un comportement sense pour tout
-- le monde, immediatement, sans toucher aux donnees.
CREATE TABLE notification_reglages (
    id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    -- Le concierge qui regle. La matrice vaut pour lui ET pour ses clients : c'est lui qui
    -- decide ce que ses proprietaires et ses prestataires recoivent, sous reserve de leur
    -- opposition individuelle (voir plus bas, qui prime).
    concierge_id BIGINT UNSIGNED NOT NULL,
    evenement    VARCHAR(40)     NOT NULL,
    -- Le role du DESTINATAIRE, pas celui de l'acteur. « Annulee / proprietaire » se lit
    -- « prevenir les proprietaires quand une intervention est annulee ».
    --
    -- VARCHAR et non ENUM : utilisateurs.role est un ENUM, et l'y aligner obligerait a un
    -- ALTER sur deux tables le jour ou un role s'ajoute.
    role         VARCHAR(20)     NOT NULL,
    actif        TINYINT(1)      NOT NULL,
    modifie_le   DATETIME        NOT NULL,
    PRIMARY KEY (id),
    -- Une seule derogation par case. C'est ce qui permet de reposer la ligne sans se
    -- demander s'il faut inserer ou mettre a jour.
    UNIQUE KEY uq_reglage_case (concierge_id, evenement, role),
    CONSTRAINT fk_reglage_concierge FOREIGN KEY (concierge_id)
        REFERENCES utilisateurs (id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- ================================================== L'OPPOSITION =================
--
-- UNE DATE ET NON UN BOOLEEN, parce qu'un booleen ne se prouve pas.
--
-- L'opposition doit pouvoir etre montree : quand a-t-elle ete enregistree ? Un TINYINT
-- repond « oui » sans dire depuis quand, et c'est precisement ce qu'on aurait a etablir.
-- La colonne porte donc l'etat ET la preuve, et NULL signifie « aucune opposition ».
--
-- ELLE PRIME SUR LA MATRICE DU CONCIERGE. Sans cela le droit n'existerait pas : un
-- reglage qui peut passer outre un refus n'est pas un refus. Mais la consequence est que
-- l'ecran du concierge doit AFFICHER que des destinataires s'y opposent — une suppression
-- silencieuse produirait exactement la dispute qu'on cherche a eviter, « je vous ai
-- prevenu » contre « je n'ai rien recu », sans arbitre.
--
-- CE QU'ELLE NE COUPE PAS : les courriels sans lesquels un compte ne fonctionne plus —
-- mot de passe oublie, invitation a definir son mot de passe, fin de contrat. Le premier
-- ne part qu'en reponse a une action de l'interesse ; le dernier est la seule explication
-- d'une porte fermee. Le fil, lui, n'est jamais coupe : l'opposition porte sur le CANAL,
-- pas sur l'information.
ALTER TABLE utilisateurs
    ADD COLUMN notifications_refus_le DATETIME NULL AFTER actif;
