/**
 * Modul: Reminders-Test
 * Zweck: Validiert DB-Schema und Abfragen für Erinnerungen
 * Ausführen: node --experimental-sqlite test-reminders.js
 */

import { DatabaseSync } from 'node:sqlite';

let passed = 0;
let failed = 0;

function test(name, fn) {
  try { fn(); console.log(`  ✓ ${name}`); passed++; }
  catch (err) { console.error(`  ✗ ${name}: ${err.message}`); failed++; }
}
function assert(cond, msg) { if (!cond) throw new Error(msg || 'Assertion fehlgeschlagen'); }

// Hilfsfunktion: SQL-Block in Einzelstatements aufteilen und ausführen
function runSQL(database, sqlBlock) {
  const statements = sqlBlock.split(';').map((s) => s.trim()).filter(Boolean);
  for (const stmt of statements) {
    database.prepare(stmt).run();
  }
}

const db = new DatabaseSync(':memory:');
db.exec('PRAGMA foreign_keys = ON;');

// Minimales Schema aufbauen: users + calendar_events + reminders
runSQL(db, `
  CREATE TABLE IF NOT EXISTS users (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    username      TEXT    UNIQUE NOT NULL,
    display_name  TEXT    NOT NULL,
    password_hash TEXT    NOT NULL,
    avatar_color  TEXT    NOT NULL DEFAULT '#007AFF',
    role          TEXT    NOT NULL DEFAULT 'member',
    created_at    TEXT    NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
    updated_at    TEXT    NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
  )
`);

runSQL(db, `
  CREATE TABLE IF NOT EXISTS tasks (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    title      TEXT    NOT NULL,
    category   TEXT    NOT NULL DEFAULT 'Sonstiges',
    status     TEXT    NOT NULL DEFAULT 'open',
    created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE
  )
`);

runSQL(db, `
  CREATE TABLE IF NOT EXISTS calendar_events (
    id               INTEGER PRIMARY KEY AUTOINCREMENT,
    title            TEXT    NOT NULL,
    start_datetime   TEXT    NOT NULL,
    created_by       INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    external_source  TEXT    NOT NULL DEFAULT 'local'
  )
`);

runSQL(db, `
  CREATE TABLE IF NOT EXISTS reminders (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    entity_type TEXT    NOT NULL CHECK(entity_type IN ('task', 'event')),
    entity_id   INTEGER NOT NULL,
    remind_at   TEXT    NOT NULL,
    dismissed   INTEGER NOT NULL DEFAULT 0,
    created_by  INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    created_at  TEXT    NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
  )
`);

runSQL(db, `
  CREATE INDEX IF NOT EXISTS idx_reminders_entity ON reminders(entity_type, entity_id)
`);

runSQL(db, `
  CREATE INDEX IF NOT EXISTS idx_reminders_remind ON reminders(remind_at)
`);

runSQL(db, `
  CREATE INDEX IF NOT EXISTS idx_reminders_user ON reminders(created_by)
`);

// Testdaten anlegen
const u1    = db.prepare(`INSERT INTO users (username, display_name, password_hash, avatar_color)
  VALUES ('admin', 'Anna', 'x', '#007AFF')`).run();
const uid1  = u1.lastInsertRowid;

const task1 = db.prepare(`INSERT INTO tasks (title, created_by) VALUES ('Steuererklärung', ?)`).run(uid1);
const taskId = task1.lastInsertRowid;

const ev1   = db.prepare(`INSERT INTO calendar_events (title, start_datetime, created_by)
  VALUES ('Zahnarzt', '2026-05-01T10:00', ?)`).run(uid1);
const evId  = ev1.lastInsertRowid;

const now    = new Date().toISOString();
const future = new Date(Date.now() + 3_600_000).toISOString().slice(0, 16);
const past   = new Date(Date.now() - 3_600_000).toISOString().slice(0, 16);

console.log('\n[Reminders-Test] Schema + CRUD + Pending-Abfragen\n');

// --------------------------------------------------------
// Erstellen
// --------------------------------------------------------

let reminderId1, reminderId2;

test('Erinnerung für Aufgabe erstellen', () => {
  const r = db.prepare(`INSERT INTO reminders (entity_type, entity_id, remind_at, created_by)
    VALUES ('task', ?, ?, ?)`).run(taskId, future, uid1);
  reminderId1 = r.lastInsertRowid;
  assert(reminderId1 > 0, 'Keine lastInsertRowid');
});

test('Erinnerung für Event erstellen', () => {
  const r = db.prepare(`INSERT INTO reminders (entity_type, entity_id, remind_at, created_by)
    VALUES ('event', ?, ?, ?)`).run(evId, past, uid1);
  reminderId2 = r.lastInsertRowid;
  assert(reminderId2 > 0, 'Keine lastInsertRowid');
});

// --------------------------------------------------------
// Lesen
// --------------------------------------------------------

test('Erinnerung nach entity_type + entity_id laden', () => {
  const row = db.prepare(`SELECT * FROM reminders
    WHERE entity_type = 'task' AND entity_id = ? AND created_by = ?`).get(taskId, uid1);
  assert(row !== null && row !== undefined, 'Keine Zeile zurückgegeben');
  assert(row.entity_type === 'task', 'Falscher entity_type');
  assert(row.dismissed === 0, 'dismissed sollte 0 sein');
});

test('Pending-Abfrage liefert nur fällige (remind_at <= now)', () => {
  const rows = db.prepare(`SELECT * FROM reminders
    WHERE created_by = ? AND dismissed = 0 AND remind_at <= ?`).all(uid1, now);
  assert(rows.length === 1, `Erwartet 1, erhalten ${rows.length}`);
  assert(rows[0].entity_type === 'event', 'Falscher entity_type in Pending-Abfrage');
});

test('Zukunfts-Reminder erscheint nicht in Pending', () => {
  const rows = db.prepare(`SELECT * FROM reminders
    WHERE created_by = ? AND dismissed = 0 AND remind_at <= ?`).all(uid1, now);
  const futureFound = rows.some((r) => r.id === reminderId1);
  assert(!futureFound, 'Zukünftiger Reminder in Pending-Liste');
});

// --------------------------------------------------------
// Verwerfen (dismiss)
// --------------------------------------------------------

test('Erinnerung verwerfen', () => {
  db.prepare(`UPDATE reminders SET dismissed = 1 WHERE id = ?`).run(reminderId2);
  const row = db.prepare(`SELECT dismissed FROM reminders WHERE id = ?`).get(reminderId2);
  assert(row.dismissed === 1, 'dismissed ist nicht 1');
});

test('Verworfene Erinnerung erscheint nicht in Pending', () => {
  const rows = db.prepare(`SELECT * FROM reminders
    WHERE created_by = ? AND dismissed = 0 AND remind_at <= ?`).all(uid1, now);
  assert(rows.length === 0, `Erwartet 0 nach Dismiss, erhalten ${rows.length}`);
});

// --------------------------------------------------------
// Ersetzen (Upsert: erst löschen, dann neu einfügen)
// --------------------------------------------------------

test('Erinnerung ersetzen: alte löschen + neue einfügen', () => {
  db.prepare(`DELETE FROM reminders WHERE entity_type = 'task' AND entity_id = ? AND created_by = ?`)
    .run(taskId, uid1);
  const newFuture = new Date(Date.now() + 7_200_000).toISOString().slice(0, 16);
  db.prepare(`INSERT INTO reminders (entity_type, entity_id, remind_at, created_by)
    VALUES ('task', ?, ?, ?)`).run(taskId, newFuture, uid1);

  const rows = db.prepare(`SELECT * FROM reminders WHERE entity_type = 'task' AND entity_id = ?`)
    .all(taskId);
  assert(rows.length === 1, `Erwartet 1 Zeile nach Ersetzen, erhalten ${rows.length}`);
});

// --------------------------------------------------------
// Löschen
// --------------------------------------------------------

test('Alle Erinnerungen einer Entität löschen', () => {
  db.prepare(`DELETE FROM reminders WHERE entity_type = 'task' AND entity_id = ? AND created_by = ?`)
    .run(taskId, uid1);
  const rows = db.prepare(`SELECT * FROM reminders WHERE entity_type = 'task' AND entity_id = ?`)
    .all(taskId);
  assert(rows.length === 0, `Erwartet 0 nach Löschen, erhalten ${rows.length}`);
});

test('Nutzer-DELETE CASCADE: Erinnerungen werden mitgelöscht', () => {
  const u2    = db.prepare(`INSERT INTO users (username, display_name, password_hash, avatar_color)
    VALUES ('temp', 'Temp', 'x', '#FF0000')`).run();
  const tempUid = u2.lastInsertRowid;

  db.prepare(`INSERT INTO reminders (entity_type, entity_id, remind_at, created_by)
    VALUES ('task', ?, ?, ?)`).run(taskId, future, tempUid);

  db.prepare(`DELETE FROM users WHERE id = ?`).run(tempUid);

  const rows = db.prepare(`SELECT * FROM reminders WHERE created_by = ?`).all(tempUid);
  assert(rows.length === 0, 'Cascade-Delete hat nicht funktioniert');
});

// --------------------------------------------------------
// Constraints
// --------------------------------------------------------

test('Ungültiger entity_type wird abgelehnt', () => {
  let threw = false;
  try {
    db.prepare(`INSERT INTO reminders (entity_type, entity_id, remind_at, created_by)
      VALUES ('invalid', 1, ?, ?)`).run(future, uid1);
  } catch {
    threw = true;
  }
  assert(threw, 'CHECK-Constraint hat entity_type nicht abgelehnt');
});

// --------------------------------------------------------
// Ergebnis
// --------------------------------------------------------

console.log(`\n  ${passed} bestanden, ${failed} fehlgeschlagen\n`);
if (failed > 0) process.exit(1);
