/**
 * Application des migrations Prisma au démarrage de la version desktop.
 *
 * POURQUOI PAS LA CLI `prisma migrate deploy` :
 * `node_modules/prisma` tire @prisma/debug, @prisma/config, @prisma/fetch-engine,
 * @prisma/get-platform, @prisma/engines-version… soit ~224 Mo à embarquer, et un
 * seul module manquant fait échouer l'installation (c'était le cas en 0.70.0 :
 * « Cannot find module '@prisma/debug' »).
 *
 * Or le client Prisma et son moteur natif sont DÉJÀ dans `.next/standalone`,
 * tracés par Next. On rejoue donc les fichiers `migration.sql` nous-mêmes, en
 * respectant le contrat de Prisma : table `_prisma_migrations`, même schéma, même
 * checksum SHA-256. Une base créée ici reste donc gérable par la CLI côté
 * développement, et inversement.
 */
'use strict';

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

/**
 * Découpe un fichier SQL en instructions.
 * Un simple `split(';')` casserait sur un `;` présent dans une chaîne littérale
 * ou un commentaire — les migrations Prisma en contiennent (`/* Warnings *​/`,
 * `DEFAULT 'actif'`).
 */
function decouperSql(sql) {
  const instructions = [];
  let courante = '';
  let i = 0;

  while (i < sql.length) {
    const c = sql[i];
    const suivant = sql[i + 1];

    // Commentaire de ligne
    if (c === '-' && suivant === '-') {
      while (i < sql.length && sql[i] !== '\n') i++;
      continue;
    }
    // Commentaire de bloc
    if (c === '/' && suivant === '*') {
      i += 2;
      while (i < sql.length && !(sql[i] === '*' && sql[i + 1] === '/')) i++;
      i += 2;
      continue;
    }
    // Chaîne littérale ('' échappe une apostrophe) ou identifiant entre guillemets
    if (c === "'" || c === '"') {
      const delim = c;
      courante += c;
      i++;
      while (i < sql.length) {
        courante += sql[i];
        if (sql[i] === delim) {
          if (sql[i + 1] === delim) { courante += sql[i + 1]; i += 2; continue; }
          i++;
          break;
        }
        i++;
      }
      continue;
    }
    // Fin d'instruction
    if (c === ';') {
      if (courante.trim()) instructions.push(courante.trim());
      courante = '';
      i++;
      continue;
    }
    courante += c;
    i++;
  }

  if (courante.trim()) instructions.push(courante.trim());
  return instructions;
}

function checksum(contenu) {
  return crypto.createHash('sha256').update(contenu).digest('hex');
}

/** Migrations présentes sur disque, dans l'ordre chronologique de leur nom. */
function listerMigrations(dossier) {
  return fs
    .readdirSync(dossier, { withFileTypes: true })
    .filter((e) => e.isDirectory())
    .map((e) => e.name)
    .sort()
    .map((nom) => {
      const fichier = path.join(dossier, nom, 'migration.sql');
      if (!fs.existsSync(fichier)) return null;
      const sql = fs.readFileSync(fichier, 'utf8');
      return { nom, sql, checksum: checksum(sql) };
    })
    .filter(Boolean);
}

/**
 * Applique les migrations manquantes.
 * @returns {Promise<{ok: boolean, appliquees: string[], raison?: string}>}
 */
async function appliquerMigrations({ prismaClientPath, migrationsDir, databaseUrl }) {
  let prisma = null;
  try {
    const { PrismaClient } = require(prismaClientPath);
    prisma = new PrismaClient({ datasources: { db: { url: databaseUrl } }, log: ['error'] });

    // Journal des migrations — schéma identique à celui de Prisma, pour rester
    // interopérable avec `migrate deploy` / `migrate status`.
    await prisma.$executeRawUnsafe(`
      CREATE TABLE IF NOT EXISTS "_prisma_migrations" (
        "id" TEXT PRIMARY KEY NOT NULL,
        "checksum" TEXT NOT NULL,
        "finished_at" DATETIME,
        "migration_name" TEXT NOT NULL,
        "logs" TEXT,
        "rolled_back_at" DATETIME,
        "started_at" DATETIME NOT NULL DEFAULT current_timestamp,
        "applied_steps_count" INTEGER UNSIGNED NOT NULL DEFAULT 0
      )
    `);

    const deja = await prisma.$queryRawUnsafe(
      `SELECT "migration_name" FROM "_prisma_migrations" WHERE "finished_at" IS NOT NULL`,
    );
    const nomsAppliques = new Set(deja.map((r) => r.migration_name));

    const appliquees = [];
    for (const m of listerMigrations(migrationsDir)) {
      if (nomsAppliques.has(m.nom)) continue;

      const instructions = decouperSql(m.sql);
      for (const sql of instructions) {
        await prisma.$executeRawUnsafe(sql);
      }

      // Enregistré APRÈS exécution : une migration interrompue ne sera pas
      // considérée comme appliquée et sera rejouée au démarrage suivant.
      await prisma.$executeRawUnsafe(
        `INSERT INTO "_prisma_migrations"
           ("id","checksum","finished_at","migration_name","started_at","applied_steps_count")
         VALUES (?, ?, current_timestamp, ?, current_timestamp, ?)`,
        crypto.randomUUID(),
        m.checksum,
        m.nom,
        instructions.length,
      );
      appliquees.push(m.nom);
    }

    return { ok: true, appliquees };
  } catch (e) {
    return { ok: false, appliquees: [], raison: String(e?.message || e).slice(0, 900) };
  } finally {
    if (prisma) { try { await prisma.$disconnect(); } catch {} }
  }
}

module.exports = { appliquerMigrations, decouperSql, listerMigrations };
