#!/usr/bin/perl
# =============================================================================
# Fabrique les migrations numerotees a partir du schema complet.
#
#   perl outils/generer-migrations.pl        depuis site/
#
# POURQUOI CET OUTIL EXISTE
# -------------------------
# Le schema complet (000-schema-complet.sql) est la source de verite : c est lui
# qu on importe sur une base neuve. Les fichiers numerotes 001, 002, 003 servent
# a rattraper une base creee a un lot precedent. Ecrits a la main, les deux
# divergent au premier ajout de colonne, et la divergence ne se voit qu au
# moment ou on en a besoin.
#
# Cet outil decoupe donc 000 en trois, table par table, en passant les CREATE
# TABLE en « IF NOT EXISTS » et les INSERT de donnees de reference en
# « INSERT IGNORE » : rejouer une migration sur une base a jour ne fait rien.
#
# A relancer apres toute modification de 000-schema-complet.sql.
# =============================================================================
use strict;
use warnings;
binmode(STDOUT, ':encoding(UTF-8)');

my $source = 'migrations/000-schema-complet.sql';
open(my $h, '<:encoding(UTF-8)', $source) or die "Fichier introuvable : $source\n";
my $sql = do { local $/; <$h> };
close $h;

# --- Le decoupage : quelle table appartient a quel lot ----------------------
my %lot = (
    '001' => {
        titre  => 'Socle : acteur, modules, session, second facteur, parametres',
        tables => [qw(migration acteur acteur_module jeton_connexion session
                      journal parametre m365_sync)],
        seeds  => [qw(acteur parametre m365_sync migration)],
    },
    '002' => {
        titre  => 'Tresorerie : comptes, charges, depenses, echeances, scenarios, remuneration',
        tables => [qw(tre_compte tre_charge_recurrente tre_depense tre_facture
                      tre_facture_ligne tre_echeance tre_reglement tre_scenario
                      tre_scenario_surcharge tre_scenario_choix tre_temps rem_poste)],
        seeds  => [qw(tre_scenario rem_poste)],
    },
    '003' => {
        titre  => 'CRM, grille de mission, PTF et chiffrage',
        tables => [qw(crm_societe crm_contact crm_affaire crm_affaire_evenement
                      crm_interaction crm_action ptf_referentiel
                      ptf_referentiel_livrable ptf ptf_element ptf_jalon
                      eco_ouvrage eco_ouvrage_composant eco_prix eco_ratio
                      eco_coefficient eco_estimation eco_estimation_ligne)],
        seeds  => [qw(eco_coefficient)],
    },
);

my %nom_fichier = (
    '001' => 'migrations/001-socle.sql',
    '002' => 'migrations/002-tresorerie.sql',
    '003' => 'migrations/003-crm-ptf.sql',
);

# --- Extraction des blocs CREATE TABLE, commentaire de tete compris ---------
# Un bloc court du dernier commentaire qui le precede jusqu au point-virgule
# final : les commentaires portent le pourquoi, ils suivent la table.
my %bloc;
while ($sql =~ /(CREATE TABLE (\w+)\s*\(.*?\n\)[^;]*;)/gs) {
    my ($corps, $table) = ($1, $2);
    my $fin = pos($sql);
    my $debut = $fin - length($corps);
    my $avant = substr($sql, 0, $debut);
    my $entete = '';
    # Seul le bloc de commentaire COLLE au CREATE compte : une ligne vide entre
    # les deux signale un commentaire qui parle d autre chose.
    if ($avant =~ /((?:^--.*\n)+)\z/m) {
        $entete = $1;
    }
    $corps =~ s/^CREATE TABLE /CREATE TABLE IF NOT EXISTS /;
    $corps .= ';' unless $corps =~ /;\s*$/;
    $bloc{$table} = $entete . $corps;
}

# --- Extraction des INSERT de donnees de reference --------------------------
my %insert;
while ($sql =~ /(INSERT INTO (\w+)[^;]*;)/gs) {
    my ($corps, $table) = ($1, $2);
    $corps =~ s/^INSERT INTO /INSERT IGNORE INTO /;
    push @{ $insert{$table} }, $corps;
}

# --- Ecriture ---------------------------------------------------------------
for my $numero (sort keys %lot) {
    my $def = $lot{$numero};
    my $chemin = $nom_fichier{$numero};

    my $texte = <<"TETE";
-- =============================================================================
-- Migration $numero : $def->{titre}
--
-- FICHIER PRODUIT PAR outils/generer-migrations.pl, a partir de
-- migrations/000-schema-complet.sql. Toute correction se fait dans le schema
-- complet, puis on relance l outil.
--
-- A QUOI IL SERT. Une base neuve s importe avec le seul 000-schema-complet.sql,
-- qui enregistre deja cette migration comme appliquee. Ce fichier sert a une
-- base creee a un lot precedent : il ajoute ce que le lot apporte, et se rejoue
-- sans dommage sur une base a jour (CREATE TABLE IF NOT EXISTS, INSERT IGNORE).
--
-- Cible : MySQL 8.4, utf8mb4, InnoDB.
-- =============================================================================

SET NAMES utf8mb4;

TETE

    for my $table (@{ $def->{tables} }) {
        if (!exists $bloc{$table}) {
            warn "Table absente du schema complet : $table\n";
            next;
        }
        $texte .= $bloc{$table} . "\n\n";
    }

    for my $table (@{ $def->{seeds} }) {
        next unless exists $insert{$table};
        next if $table eq 'migration';
        $texte .= join("\n\n", @{ $insert{$table} }) . "\n\n";
    }

    $texte .= "INSERT IGNORE INTO migration (numero, libelle) VALUES\n"
            . "    ('$numero', '$def->{titre}');\n";

    open(my $o, '>:encoding(UTF-8)', $chemin) or die "Ecriture impossible : $chemin\n";
    print $o $texte;
    close $o;
    printf("%s : %d table(s).\n", $chemin, scalar @{ $def->{tables} });
}

# --- Controle : aucune table du schema ne doit rester sans lot --------------
my %vues;
for my $numero (keys %lot) { $vues{$_} = 1 for @{ $lot{$numero}{tables} }; }
my @orphelines = grep { !$vues{$_} } sort keys %bloc;
if (@orphelines) {
    print "\nATTENTION : table(s) du schema complet rattachee(s) a aucun lot :\n";
    print "  $_\n" for @orphelines;
    exit 1;
}
print "\nToutes les tables du schema complet sont rattachees a un lot.\n";
exit 0;
