import Database from 'better-sqlite3';
import { mkdirSync } from 'node:fs';
import { dirname } from 'node:path';
import { randomUUID } from 'node:crypto';
import { contentSeed } from '../data/content-seed';

const SCHEMA = `
CREATE TABLE IF NOT EXISTS posts (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  slug TEXT NOT NULL UNIQUE,
  category TEXT NOT NULL,
  excerpt TEXT NOT NULL DEFAULT '',
  body_html TEXT NOT NULL DEFAULT '',
  cover TEXT,
  draft INTEGER NOT NULL DEFAULT 1,
  published_at TEXT,
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT NOT NULL UNIQUE,
  password_hash TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS sessions (
  token TEXT PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  expires_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_posts_pub ON posts (draft, published_at DESC);
CREATE TABLE IF NOT EXISTS content_blocks (
  tag        TEXT PRIMARY KEY,
  type       TEXT NOT NULL CHECK (type IN ('text','html','image')),
  value      TEXT NOT NULL DEFAULT '',
  styles     TEXT NOT NULL DEFAULT '{}',
  updated_at TEXT NOT NULL DEFAULT (datetime('now')),
  updated_by TEXT
);
`;

export function createDb(path?: string): Database.Database {
  const p = path ?? process.env.DB_PATH ?? 'data/insanitylab.db';
  if (p !== ':memory:') mkdirSync(dirname(p), { recursive: true });
  const db = new Database(p);
  db.pragma('journal_mode = WAL');
  db.pragma('foreign_keys = ON');
  db.exec(SCHEMA);
  // Migrazione additiva: DB creati prima dell'introduzione dei ruoli non hanno users.role.
  const userCols = db.prepare(`PRAGMA table_info(users)`).all() as { name: string }[];
  if (!userCols.some((c) => c.name === 'role')) {
    db.exec(`ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'admin'`);
  }
  // Ruolo editor unificato in superuser (3 ruoli: admin, superuser, user). Idempotente.
  db.prepare("UPDATE users SET role = 'superuser' WHERE role = 'editor'").run();

  // GUID stabile per ogni contenuto taggato: colonna additiva + backfill + indice univoco.
  const contentCols = db.prepare(`PRAGMA table_info(content_blocks)`).all() as { name: string }[];
  if (!contentCols.some((c) => c.name === 'guid')) {
    db.exec(`ALTER TABLE content_blocks ADD COLUMN guid TEXT`);
  }

  // Autore articoli: colonna nullable (post storici restano senza autore).
  const postCols = db.prepare(`PRAGMA table_info(posts)`).all() as { name: string }[];
  if (!postCols.some((c) => c.name === 'author_id')) {
    db.exec(`ALTER TABLE posts ADD COLUMN author_id INTEGER`);
  }
  // Migrazione: la sezione "Programmi" è diventata "Servizi" → rinomina dei tag esistenti
  // preservando le modifiche fatte dal pannello. Ordine: prima i prefissi più specifici.
  // UPDATE OR IGNORE salta le righe in conflitto (tag nuovo già presente); la DELETE
  // successiva ripulisce le righe vecchie rimaste. Idempotente: al secondo giro nessun match.
  const TAG_RENAMES: [string, string][] = [
    ['home.programs.', 'home.services.'],
    ['footer.programs.', 'footer.services.'],
    ['programs.', 'services.'],
    ['program.', 'service.'],
  ];
  const renameUpd = db.prepare('UPDATE OR IGNORE content_blocks SET tag = ? || substr(tag, ?) WHERE tag LIKE ?');
  const renameDel = db.prepare('DELETE FROM content_blocks WHERE tag LIKE ?');
  const renameTx = db.transaction(() => {
    for (const [oldP, newP] of TAG_RENAMES) {
      renameUpd.run(newP, oldP.length + 1, `${oldP}%`);
      renameDel.run(`${oldP}%`);
    }
    // Tag singolo, non coperto dai prefissi: uguaglianza esatta.
    db.prepare("UPDATE OR IGNORE content_blocks SET tag = 'footer.col.services-title' WHERE tag = 'footer.col.programs-title'").run();
    db.prepare("DELETE FROM content_blocks WHERE tag = 'footer.col.programs-title'").run();

    // I valori di default che nominavano la sezione cambiano con la rinomina. Il seed è
    // INSERT OR IGNORE e non tocca le righe esistenti, quindi si aggiornano qui — ma solo
    // le righe rimaste identiche al valore originale del seed (mai modificate dal pannello):
    // il nuovo valore è quello del seed corrente per lo stesso tag.
    const OLD_SECTION_DEFAULTS: [string, string][] = [
      ['global.nav.3.label', 'Programmi'],
      ['footer.col.services-title', 'Programmi'],
      ['home.services.title', 'Programmi'],
      ['home.services.body', 'Questi sono i programmi delle classi di Insanity Lab selezionati per voi. Abbiamo scelto i migliori percorsi per offrirvi un’esperienza di allenamento completa e mirata.'],
      ['services.meta.title', 'Programmi'],
      ['services.meta.description', 'I programmi InsanityLab: Personal Training, Performance Class, Coaching, Pilates Flow, Rehab e Nutrizione.'],
      ['services.hero.title', 'Programmi'],
      ["services.intro.body", "Questi sono i programmi delle classi di Insanity Lab selezionati per voi. Abbiamo scelto i migliori percorsi per offrirvi un'esperienza di allenamento completa e mirata."],
      ['service.side.others-label', 'Altri programmi'],
      ['service.pricing.empty-note', 'Contattaci per i dettagli di questo programma. <a href="/contact">Scrivici →</a>'],
    ];
    const seedValue = new Map(contentSeed.map((e) => [e.tag, e.value]));
    const updValue = db.prepare('UPDATE content_blocks SET value = ? WHERE tag = ? AND value = ?');
    for (const [tag, oldValue] of OLD_SECTION_DEFAULTS) {
      const next = seedValue.get(tag);
      if (next !== undefined) updValue.run(next, tag, oldValue);
    }
  });
  renameTx();

  // Modifiche 03: "Personal Training" → "One to One" (nuovo slug one-to-one). Rinomina i tag
  // esistenti prima del seed per preservare le modifiche da pannello; il title di default viene
  // aggiornato solo se non modificato. La nuova pagina "Small Group" (slug small-group) è un
  // servizio nuovo e viene inserita dal seed più sotto. Idempotente.
  const oneToOneTx = db.transaction(() => {
    renameUpd.run('service.one-to-one.', 'service.personal-training.'.length + 1, 'service.personal-training.%');
    renameDel.run('service.personal-training.%');
    db.prepare("UPDATE content_blocks SET value = 'One to One' WHERE tag = 'service.one-to-one.title' AND value = 'Personal Training'").run();
  });
  oneToOneTx();

  const ins = db.prepare('INSERT OR IGNORE INTO content_blocks (tag, type, value, guid) VALUES (?, ?, ?, ?)');
  const syncTx = db.transaction(() => { for (const e of contentSeed) ins.run(e.tag, e.type, e.value, randomUUID()); });
  syncTx();

  // Modifiche 02 (pagina Servizi): "Pilates Flow" → "Profilazione" e "Rehab" → "Osteopatia".
  // I nuovi servizi hanno slug nuovi (tag service.profilazione.* / service.osteopatia.*) e
  // vengono inseriti dal seed qui sopra. Restano da aggiornare le righe EXISTING il cui valore
  // cambia: label del footer e meta description. UPDATE guardato dal vecchio valore → idempotente
  // e non sovrascrive eventuali modifiche fatte dal pannello.
  const SERVIZI_02: [string, string, string][] = [
    ['footer.services.4.label', 'Rehab', 'Profilazione'],
    ['footer.services.5.label', 'Pilates Flow', 'Osteopatia'],
    ['services.meta.description',
      'I servizi InsanityLab: Personal Training, Performance Class, Coaching, Pilates Flow, Rehab e Nutrizione.',
      'I servizi InsanityLab: Personal Training, Performance Class, Coaching, Profilazione, Osteopatia e Nutrizione.'],
    // Pagina Training: riquadri passati a One to One, Small Group, Rehab, Holistic Class,
    // Reformer Class, Coaching. Label footer + meta aggiornate (righe 5-6 sono nuovi tag).
    ['footer.training.2.label', 'Small groups', 'Small group'],
    ['footer.training.3.label', 'Performance class', 'Rehab'],
    ['footer.training.4.label', 'Fit remote', 'Holistic class'],
    ['training.meta.description',
      'One to One, Small Groups, Performance Class e Fit Remote: trova il training InsanityLab più adatto a te.',
      'One to One, Small Group, Rehab, Holistic Class, Reformer Class e Coaching: trova il training InsanityLab più adatto a te.'],
  ];
  const upd02 = db.prepare('UPDATE content_blocks SET value = ? WHERE tag = ? AND value = ?');
  const upd02Tx = db.transaction(() => { for (const [tag, oldV, newV] of SERVIZI_02) upd02.run(newV, tag, oldV); });
  upd02Tx();

  // Metodo (home): step riscritti dal pannello in dev ma mai propagati in produzione
  // (il DB non è versionato: gli edit vivono solo nel volume). Qui si allineano le righe
  // di prod ancora ai vecchi default del seed, senza toccare eventuali modifiche successive.
  const METODO: [string, string, string][] = [
    ['method.step.1.text', 'Dove raccogliamo un’anamnesi iniziale per comprendere il cliente, i suoi obiettivi e guidarlo verso il percorso più adatto.', 'La base di partenza per definire le priorità del percorso di ciascun cliente.'],
    ['method.step.2.title', 'Prima seduta operativa', 'Check-up fisico'],
    ['method.step.2.text', 'In cui si effettua la profilazione iniziale e la valutazione sul campo per definire il punto di partenza e impostare un percorso personalizzato.', 'Profilazione fisica per definire lo stato di salute iniziale del cliente.'],
    ['method.step.3.title', 'Plicometria', 'Insanity plan'],
    ['method.step.3.text', 'Test che misura il grasso sottocutaneo per stimare la massa grassa, disponibile su richiesta o come servizio aggiuntivo.', 'Definizione del programma personalizzato con protocolli scientificamente validati costruiti sul cliente.'],
    ['method.step.4.title', 'Analisi antropometriche', 'Monitoraggio costante'],
    ['method.step.4.text', 'Le analisi antropometriche sono misurazioni del corpo utili a valutare la composizione fisica e lo stato di forma.', 'Monitoraggio live durante le sessioni di allenamento attraverso strumentazione e sensoristica avanzata. Misurazione periodica dei progressi.'],
  ];
  const updMetodoTx = db.transaction(() => { for (const [tag, oldV, newV] of METODO) upd02.run(newV, tag, oldV); });
  updMetodoTx();

  // Modifiche 03: due ingressi dello stesso stabile → indirizzo esteso in footer e contatti.
  // Il tag global.contact.map-query è nuovo (inserito dal seed); qui si aggiorna solo l'indirizzo
  // già esistente, se non modificato dal pannello. Idempotente.
  db.prepare("UPDATE content_blocks SET value = 'Via Leandro Alberti 76 e Via De Mattiolo 5' WHERE tag = 'global.contact.address' AND value = 'Via Leandro Alberti, 76'").run();

  // Modifiche 02 (pagina Insanitylab/about): testi riscritti. Guardia via frammento LIKE
  // distintivo del vecchio testo → robusto agli apostrofi tipografici, idempotente e
  // rispettoso delle eventuali riscritture da pannello (se il frammento non c'è più, salta).
  const ABOUT_02: [string, string, string][] = [
    ['about.about.body', '%unire fitness e salute in un unico percorso%',
      "InsanityLab nasce dalla visione di creare uno spazio dove il fitness incontrasse la salute in modo reale e profondo, non superficiale. L'idea di partenza era semplice ma rivoluzionaria nel settore: il movimento non dovrebbe essere vissuto come un sacrificio estetico, ma come un atto di cura verso se stessi. Da questa convinzione è nato un laboratorio esclusivo di benessere, pensato da chi conosce il corpo in profondità — non solo come semplici trainer, ma come osteopati e chinesiologi esperti del movimento."],
    ['about.convenzioni.item1', '%small group training e classi di gruppo%',
      'Le realtà convenzionate possono accedere a programmi di welfare aziendale personalizzati.'],
    ['about.convenzioni.item2', '%consulenza gratuita preliminare%',
      'Ogni collaborazione parte da una consulenza preliminare per analizzare esigenze e obiettivi, così da sviluppare soluzioni efficaci e sostenibili.'],
    ['about.studios.body1', '%della performance. Più che un semplice insieme%',
      "IN-SANITY STUDIOS è il nuovo spazio professionale nato dall'evoluzione di InsanityLab, dedicato ai professionisti della salute e del benessere."],
  ];
  const updAbout = db.prepare('UPDATE content_blocks SET value = ? WHERE tag = ? AND value LIKE ?');
  const updAboutTx = db.transaction(() => { for (const [tag, frag, newV] of ABOUT_02) updAbout.run(newV, tag, frag); });
  updAboutTx();
  // Backfill: righe pre-esistenti (già presenti prima dell'introduzione del guid) restano
  // ignorate dall'INSERT OR IGNORE e hanno guid nullo → assegna un UUID a ciascuna.
  const missing = db.prepare(`SELECT tag FROM content_blocks WHERE guid IS NULL OR guid = ''`).all() as { tag: string }[];
  const backfill = db.prepare('UPDATE content_blocks SET guid = ? WHERE tag = ?');
  const bfTx = db.transaction(() => { for (const m of missing) backfill.run(randomUUID(), m.tag); });
  bfTx();
  // Indice univoco creato dopo il backfill, così non fallisce su righe a guid nullo.
  db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_content_guid ON content_blocks(guid)`);
  return db;
}

let singleton: Database.Database | null = null;

export function getDb(): Database.Database {
  if (!singleton) singleton = createDb();
  return singleton;
}
