import { DatabaseSync } from 'node:sqlite';
import { chmodSync, existsSync } from 'node:fs';

const MIGRATIONS: Array<{ id: number; sql: string }> = [
  {
    id: 1,
    sql: `
      CREATE TABLE settings (
        key   TEXT PRIMARY KEY,
        value TEXT NOT NULL
      );
      CREATE TABLE auth_tokens (
        id           TEXT PRIMARY KEY,
        label        TEXT NOT NULL,
        token_hash   TEXT NOT NULL UNIQUE,
        created_at   TEXT NOT NULL,
        last_used_at TEXT,
        revoked_at   TEXT
      );
      CREATE TABLE sessions (
        id         TEXT PRIMARY KEY,
        cwd        TEXT NOT NULL,
        command    TEXT NOT NULL,
        title      TEXT,
        created_at TEXT NOT NULL,
        ended_at   TEXT,
        exit_code  INTEGER
      );
    `,
  },
  {
    // P2 — découverte & reprise : corrélation avec les sessions Claude sur disque.
    id: 2,
    sql: `
      ALTER TABLE sessions ADD COLUMN claude_session_id TEXT;
      ALTER TABLE sessions ADD COLUMN resumed_from TEXT;
      CREATE INDEX idx_sessions_claude_session_id ON sessions(claude_session_id);
    `,
  },
  {
    // P3 — repos enregistrés. Les worktrees sont dérivés à la volée de git (non persistés).
    id: 3,
    sql: `
      CREATE TABLE repos (
        id                TEXT PRIMARY KEY,
        path              TEXT NOT NULL UNIQUE,
        label             TEXT NOT NULL,
        default_branch    TEXT,
        post_create_hooks TEXT NOT NULL DEFAULT '[]',
        pre_trust         INTEGER NOT NULL DEFAULT 0,
        created_at        TEXT NOT NULL
      );
    `,
  },
  {
    // P4 — abonnements Web Push. Liés au token d'auth (token_id) ; clés VAPID en settings.
    id: 4,
    sql: `
      CREATE TABLE push_subscriptions (
        id         TEXT PRIMARY KEY,
        token_id   TEXT NOT NULL,
        endpoint   TEXT NOT NULL UNIQUE,
        p256dh     TEXT NOT NULL,
        auth       TEXT NOT NULL,
        user_agent TEXT,
        created_at TEXT NOT NULL,
        last_ok_at TEXT
      );
      CREATE INDEX idx_push_subs_token ON push_subscriptions(token_id);
    `,
  },
  {
    // P5 — groupes de travail. Un groupe = collection de repos (many-to-many).
    // Worktrees/sessions NON persistés ici : servis par WorktreeManager/PtyManager
    // et filtrés côté client par repoId. CASCADE s'appuie sur PRAGMA foreign_keys = ON (cf. openDb).
    id: 5,
    sql: `
      CREATE TABLE groups (
        id          TEXT PRIMARY KEY,
        label       TEXT NOT NULL,
        description TEXT,
        color       TEXT,
        position    INTEGER NOT NULL DEFAULT 0,
        created_at  TEXT NOT NULL,
        updated_at  TEXT NOT NULL
      );
      CREATE TABLE group_repos (
        group_id   TEXT NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
        repo_id    TEXT NOT NULL REFERENCES repos(id)  ON DELETE CASCADE,
        position   INTEGER NOT NULL DEFAULT 0,
        created_at TEXT NOT NULL,
        PRIMARY KEY (group_id, repo_id)
      );
      CREATE INDEX idx_group_repos_repo ON group_repos(repo_id);
    `,
  },
  {
    // Découverte auto : un repo masqué reste en DB (exclu du dashboard) pour qu'un
    // re-scan ne le ressuscite pas. hidden=1 = masqué.
    id: 6,
    sql: `ALTER TABLE repos ADD COLUMN hidden INTEGER NOT NULL DEFAULT 0;`,
  },
  {
    // Journal d'audit (conformité entreprise : GDPR/SOX/ISO 27001). Trace les mutations
    // sensibles (tokens, réglages, secrets, abonnements push, groupes). Ne contient JAMAIS
    // de secret en clair — `details` est un JSON de métadonnées non sensibles.
    id: 7,
    sql: `
      CREATE TABLE audit_logs (
        id          TEXT PRIMARY KEY,
        ts          TEXT NOT NULL,
        actor       TEXT NOT NULL,
        action      TEXT NOT NULL,
        resource_id TEXT,
        details     TEXT,
        result      TEXT NOT NULL
      );
      CREATE INDEX idx_audit_ts ON audit_logs(ts);
    `,
  },
];

export type Db = DatabaseSync;

export function openDb(path: string): Db {
  const db = new DatabaseSync(path);
  db.exec('PRAGMA journal_mode = WAL');
  db.exec('PRAGMA foreign_keys = ON');
  hardenDbPermissions(path);
  migrate(db);
  return db;
}

/**
 * Restreint la base (et ses fichiers WAL/SHM) à 0o600 — proprio uniquement. La base contient des
 * secrets (server_secret, clé privée VAPID, hashs de tokens) : elle ne doit jamais être lisible par
 * d'autres utilisateurs du système. Best-effort : ignoré sur les FS sans permissions POSIX.
 */
function hardenDbPermissions(path: string): void {
  if (path === ':memory:') return;
  for (const p of [path, `${path}-wal`, `${path}-shm`]) {
    try {
      if (existsSync(p)) chmodSync(p, 0o600);
    } catch {
      /* FS sans permissions POSIX (Windows / montage) : ignoré */
    }
  }
}

function migrate(db: DatabaseSync): void {
  db.exec('CREATE TABLE IF NOT EXISTS schema_migrations (id INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)');
  const applied = new Set(
    (db.prepare('SELECT id FROM schema_migrations').all() as Array<{ id: number }>).map((r) => r.id),
  );
  for (const m of MIGRATIONS) {
    if (applied.has(m.id)) continue;
    db.exec('BEGIN');
    try {
      db.exec(m.sql);
      db.prepare('INSERT INTO schema_migrations (id, applied_at) VALUES (?, ?)').run(m.id, new Date().toISOString());
      db.exec('COMMIT');
    } catch (err) {
      db.exec('ROLLBACK');
      throw err;
    }
  }
}

export function getSetting(db: Db, key: string): string | null {
  const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined;
  return row?.value ?? null;
}

export function setSetting(db: Db, key: string, value: string): void {
  db.prepare('INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);
}
