// ─────────────────────────────────────────────────────────────
// src/db.js — SQLite initialization, schema, prepared queries.
//
// Uses better-sqlite3 (synchronous, in-process). The DB file lives
// at ../data/fenja.sqlite and is created automatically on first run.
// WAL mode is enabled so concurrent readers don't block the writer.
// ─────────────────────────────────────────────────────────────
import Database from 'better-sqlite3';
import path from 'node:path';
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const dataDir = path.join(__dirname, '..', 'data');
fs.mkdirSync(dataDir, { recursive: true });

const db = new Database(path.join(dataDir, 'fenja.sqlite'));
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
db.pragma('synchronous = NORMAL'); // safe with WAL, faster

// ─── Schema ──────────────────────────────────────────────────
db.exec(`
  CREATE TABLE IF NOT EXISTS invites (
    email       TEXT PRIMARY KEY,
    invited_at  INTEGER NOT NULL,
    invited_by  TEXT
  );

  CREATE TABLE IF NOT EXISTS sessions (
    id          TEXT PRIMARY KEY,
    email       TEXT NOT NULL,
    issued_at   INTEGER NOT NULL,
    expires_at  INTEGER NOT NULL,
    ip          TEXT,
    user_agent  TEXT
  );

  CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
  CREATE INDEX IF NOT EXISTS idx_sessions_email   ON sessions(email);

  CREATE TABLE IF NOT EXISTS rate_limits (
    key         TEXT PRIMARY KEY,
    count       INTEGER NOT NULL,
    window_end  INTEGER NOT NULL
  );

  CREATE INDEX IF NOT EXISTS idx_rate_window ON rate_limits(window_end);

  CREATE TABLE IF NOT EXISTS bifrost_joins (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    email       TEXT NOT NULL,
    clicked_at  INTEGER NOT NULL,
    session_id  TEXT
  );

  CREATE INDEX IF NOT EXISTS idx_bifrost_joins_email      ON bifrost_joins(email);
  CREATE INDEX IF NOT EXISTS idx_bifrost_joins_clicked_at ON bifrost_joins(clicked_at);
`);

// ─── Migrations ──────────────────────────────────────────────
// In-place column additions for databases created by earlier versions.
// Each block is idempotent — checks for the column before altering.
//
// Migration 1: add `first_name` to invites. Introduced when the login
// flow switched from code-based verification to email-only lookup; the
// first name lives on the invite so the welcome screen can greet the
// user by name. NULL for legacy rows is fine — the frontend falls back
// to an anonymous copy when first_name is null.
const inviteCols = db.prepare(`PRAGMA table_info(invites)`).all();
if (!inviteCols.some((c) => c.name === 'first_name')) {
  db.exec(`ALTER TABLE invites ADD COLUMN first_name TEXT`);
}

// Migration 2: drop the old `codes` table entirely. The code-based
// verification flow was removed when auth simplified to email-only
// login against the invite list. Safe to drop unconditionally —
// worst case this is a no-op on a fresh DB.
db.exec(`DROP TABLE IF EXISTS codes`);
db.exec(`DROP INDEX IF EXISTS idx_codes_expires`);

// Migration 3: bifrost_joins schema expanded to one-row-per-click.
// The first version of the table used `email` as PRIMARY KEY with
// INSERT OR IGNORE — only the first click was recorded per user.
// The new table has an auto-increment `id` so every click is stored,
// enabling per-user click history. Detect the old schema by the
// absence of an `id` column; if found, rename, rebuild, copy, drop.
const joinCols = db.prepare(`PRAGMA table_info(bifrost_joins)`).all();
if (joinCols.length > 0 && !joinCols.some((c) => c.name === 'id')) {
  db.exec(`
    ALTER TABLE bifrost_joins RENAME TO bifrost_joins_old;
    CREATE TABLE bifrost_joins (
      id          INTEGER PRIMARY KEY AUTOINCREMENT,
      email       TEXT NOT NULL,
      clicked_at  INTEGER NOT NULL,
      session_id  TEXT
    );
    INSERT INTO bifrost_joins (email, clicked_at, session_id)
      SELECT email, clicked_at, session_id FROM bifrost_joins_old;
    DROP TABLE bifrost_joins_old;
    CREATE INDEX IF NOT EXISTS idx_bifrost_joins_email      ON bifrost_joins(email);
    CREATE INDEX IF NOT EXISTS idx_bifrost_joins_clicked_at ON bifrost_joins(clicked_at);
  `);
}

// ─── Prepared statements ─────────────────────────────────────
export const q = {
  // invites
  getInvite: db.prepare('SELECT email, first_name FROM invites WHERE email = ?'),
  upsertInvite: db.prepare(
    `INSERT INTO invites (email, first_name, invited_at, invited_by) VALUES (?, ?, ?, ?)
     ON CONFLICT(email) DO UPDATE SET
       first_name = excluded.first_name`
  ),
  deleteInvite: db.prepare('DELETE FROM invites WHERE email = ?'),
  listInvites: db.prepare(
    `SELECT email, first_name, invited_at, invited_by
     FROM invites ORDER BY invited_at DESC`
  ),

  // sessions
  createSession: db.prepare(
    `INSERT INTO sessions (id, email, issued_at, expires_at, ip, user_agent)
     VALUES (?, ?, ?, ?, ?, ?)`
  ),
  getSession: db.prepare('SELECT id, email, issued_at, expires_at FROM sessions WHERE id = ? AND expires_at > ?'),
  deleteSession: db.prepare('DELETE FROM sessions WHERE id = ?'),
  deleteSessionsForEmail: db.prepare('DELETE FROM sessions WHERE email = ?'),

  // rate limits
  getRate: db.prepare('SELECT count, window_end FROM rate_limits WHERE key = ?'),
  setRate: db.prepare(
    `INSERT INTO rate_limits (key, count, window_end) VALUES (?, ?, ?)
     ON CONFLICT(key) DO UPDATE SET
       count      = excluded.count,
       window_end = excluded.window_end`
  ),

  // bifrost joins — one row per click of the final CTA. Every press
  // is logged with the user's email, timestamp, and session ID so the
  // full history is available per user and in aggregate.
  recordJoin: db.prepare(
    `INSERT INTO bifrost_joins (email, clicked_at, session_id) VALUES (?, ?, ?)`
  ),
  listJoins: db.prepare(
    `SELECT id, email, clicked_at, session_id FROM bifrost_joins ORDER BY clicked_at DESC`
  ),
  listJoinsForEmail: db.prepare(
    `SELECT id, clicked_at, session_id FROM bifrost_joins WHERE email = ? ORDER BY clicked_at DESC`
  ),
  // Per-user summary: most recent click first, with per-user counts.
  summariseJoins: db.prepare(
    `SELECT email,
            COUNT(*)        AS click_count,
            MIN(clicked_at) AS first_clicked_at,
            MAX(clicked_at) AS last_clicked_at
     FROM bifrost_joins
     GROUP BY email
     ORDER BY last_clicked_at DESC`
  ),
  countJoins:        db.prepare(`SELECT COUNT(*)           AS n FROM bifrost_joins`),
  countUniqueJoiners: db.prepare(`SELECT COUNT(DISTINCT email) AS n FROM bifrost_joins`),

  // cleanup
  cleanup: {
    sessions: db.prepare('DELETE FROM sessions     WHERE expires_at < ?'),
    rates:    db.prepare('DELETE FROM rate_limits  WHERE window_end < ?'),
  },
};

// ─── Periodic cleanup ────────────────────────────────────────
// Every five minutes, prune expired rows. unref() so this timer
// doesn't keep the process alive on shutdown.
const CLEANUP_EVERY_MS = 5 * 60 * 1000;
setInterval(() => {
  const now = Date.now();
  q.cleanup.sessions.run(now);
  q.cleanup.rates.run(now);
}, CLEANUP_EVERY_MS).unref();

export default db;
