import Database from 'better-sqlite3';
import fs from 'fs';
import { paths, DATA_DIR } from '../shared/paths.js';

const SCHEMA = `
CREATE TABLE IF NOT EXISTS conversations (
  id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
  title TEXT,
  model TEXT,
  session_id TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS messages (
  id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
  conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
  role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
  content TEXT NOT NULL,
  tokens_in INTEGER,
  tokens_out INTEGER,
  model TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_msg_conv ON messages(conversation_id, created_at);
CREATE TABLE IF NOT EXISTS settings (
  key TEXT PRIMARY KEY,
  value TEXT NOT NULL,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS sessions (
  token TEXT PRIMARY KEY,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  expires_at DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS push_subscriptions (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  endpoint TEXT NOT NULL UNIQUE,
  keys_p256dh TEXT NOT NULL,
  keys_auth TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS trusted_devices (
  id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
  token TEXT NOT NULL UNIQUE,
  label TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  expires_at DATETIME NOT NULL,
  last_seen DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_td_token ON trusted_devices(token);
`;

let db: Database.Database;

export function initDb(): void {
  fs.mkdirSync(DATA_DIR, { recursive: true });
  db = new Database(paths.db);
  db.pragma('journal_mode = WAL');
  db.pragma('foreign_keys = ON');
  db.exec(SCHEMA);

  // Migration: add session_id column if missing (existing DBs)
  const cols = db.prepare("PRAGMA table_info(conversations)").all() as { name: string }[];
  if (!cols.some((c) => c.name === 'session_id')) {
    db.exec('ALTER TABLE conversations ADD COLUMN session_id TEXT');
  }

  // Migration: add audio_data column if missing (voice messages)
  const msgCols = db.prepare("PRAGMA table_info(messages)").all() as { name: string }[];
  if (!msgCols.some((c) => c.name === 'audio_data')) {
    db.exec('ALTER TABLE messages ADD COLUMN audio_data TEXT');
  }

  // Migration: add attachments column if missing (persistent file attachments)
  const msgCols2 = db.prepare("PRAGMA table_info(messages)").all() as { name: string }[];
  if (!msgCols2.some((c) => c.name === 'attachments')) {
    db.exec('ALTER TABLE messages ADD COLUMN attachments TEXT');
  }
}

export function closeDb(): void { db?.close(); }

// Conversations
export function createConversation(title?: string, model?: string) {
  return db.prepare('INSERT INTO conversations (title, model) VALUES (?, ?) RETURNING *').get(title ?? null, model ?? null) as any;
}
export function listConversations(limit = 50) {
  return db.prepare('SELECT * FROM conversations ORDER BY updated_at DESC LIMIT ?').all(limit);
}
export function deleteConversation(id: string) {
  db.prepare('DELETE FROM conversations WHERE id = ?').run(id);
}

// Messages
export function addMessage(convId: string, role: string, content: string, meta?: { tokens_in?: number; tokens_out?: number; model?: string; audio_data?: string; attachments?: string }) {
  // Self-heal: if the conversation row is missing (orphan live convId, harness session
  // drift, deleted parent, etc.), create it so the FK constraint never fires.
  // Use the first user message as title; assistant-first stays NULL (filled by UI).
  const tx = db.transaction(() => {
    db.prepare('INSERT OR IGNORE INTO conversations (id, title, model) VALUES (?, ?, ?)')
      .run(convId, role === 'user' ? content.slice(0, 80) : null, meta?.model ?? null);
    const msg = db.prepare('INSERT INTO messages (conversation_id, role, content, tokens_in, tokens_out, model, audio_data, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING *')
      .get(convId, role, content, meta?.tokens_in ?? null, meta?.tokens_out ?? null, meta?.model ?? null, meta?.audio_data ?? null, meta?.attachments ?? null);
    db.prepare('UPDATE conversations SET updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(convId);
    return msg;
  });
  return tx() as any;
}
export function getMessages(convId: string) {
  return db.prepare('SELECT * FROM messages WHERE conversation_id = ? ORDER BY created_at ASC').all(convId);
}

// Settings
export function getSetting(key: string): string | undefined {
  return (db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as any)?.value;
}
export function setSetting(key: string, value: string) {
  db.prepare('INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = CURRENT_TIMESTAMP').run(key, value);
}
export function getAllSettings() {
  const rows = db.prepare('SELECT key, value FROM settings').all() as { key: string; value: string }[];
  return Object.fromEntries(rows.map((r) => [r.key, r.value]));
}

// Auth sessions
export function createSession(token: string, expiresAt: string) {
  db.prepare('INSERT INTO sessions (token, expires_at) VALUES (?, ?)').run(token, expiresAt);
}
export function getSession(token: string): { token: string; created_at: string; expires_at: string } | undefined {
  return db.prepare('SELECT * FROM sessions WHERE token = ? AND expires_at > datetime(\'now\')').get(token) as any;
}
export function deleteSession(token: string) {
  db.prepare('DELETE FROM sessions WHERE token = ?').run(token);
}
export function deleteExpiredSessions() {
  db.prepare('DELETE FROM sessions WHERE expires_at <= datetime(\'now\')').run();
}

// Session ID (Agent SDK)
export function getSessionId(convId: string): string | null {
  const row = db.prepare('SELECT session_id FROM conversations WHERE id = ?').get(convId) as any;
  return row?.session_id ?? null;
}
export function saveSessionId(convId: string, sessionId: string): void {
  db.prepare('UPDATE conversations SET session_id = ? WHERE id = ?').run(sessionId, convId);
}

// Push subscriptions
export function addPushSubscription(endpoint: string, p256dh: string, auth: string) {
  db.prepare(
    `INSERT INTO push_subscriptions (endpoint, keys_p256dh, keys_auth) VALUES (?, ?, ?)
     ON CONFLICT(endpoint) DO UPDATE SET keys_p256dh = excluded.keys_p256dh, keys_auth = excluded.keys_auth`
  ).run(endpoint, p256dh, auth);
}
export function removePushSubscription(endpoint: string) {
  db.prepare('DELETE FROM push_subscriptions WHERE endpoint = ?').run(endpoint);
}
export function getAllPushSubscriptions() {
  return db.prepare('SELECT * FROM push_subscriptions').all() as { id: number; endpoint: string; keys_p256dh: string; keys_auth: string }[];
}
export function getPushSubscriptionByEndpoint(endpoint: string) {
  return db.prepare('SELECT * FROM push_subscriptions WHERE endpoint = ?').get(endpoint) as { id: number; endpoint: string; keys_p256dh: string; keys_auth: string } | undefined;
}

// Trusted devices (2FA)
export function createTrustedDevice(token: string, label: string, expiresAt: string) {
  return db.prepare('INSERT INTO trusted_devices (token, label, expires_at) VALUES (?, ?, ?) RETURNING *').get(token, label, expiresAt) as any;
}
export function getTrustedDevice(token: string): { id: string; token: string; label: string; created_at: string; expires_at: string; last_seen: string } | undefined {
  return db.prepare("SELECT * FROM trusted_devices WHERE token = ? AND expires_at > datetime('now')").get(token) as any;
}
export function updateDeviceLastSeen(token: string) {
  db.prepare("UPDATE trusted_devices SET last_seen = CURRENT_TIMESTAMP WHERE token = ?").run(token);
}
export function listTrustedDevices() {
  return db.prepare("SELECT id, label, last_seen, created_at FROM trusted_devices WHERE expires_at > datetime('now') ORDER BY last_seen DESC").all() as { id: string; label: string; last_seen: string; created_at: string }[];
}
export function deleteTrustedDevice(id: string) {
  db.prepare('DELETE FROM trusted_devices WHERE id = ?').run(id);
}
export function deleteExpiredDevices() {
  db.prepare("DELETE FROM trusted_devices WHERE expires_at <= datetime('now')").run();
}
export function deleteAllTrustedDevices() {
  db.prepare('DELETE FROM trusted_devices').run();
}

// Recent messages (for context injection).
// Order by rowid (monotonic insertion order) — created_at has 1-second resolution
// so rapid-fire messages can collide. rowid never does.
// rowid is a hidden column, so `SELECT *` omits it — we must alias it explicitly
// in the inner query for the outer ORDER BY to reach it.
export function getRecentMessages(convId: string, limit = 20) {
  return db.prepare(`
    SELECT * FROM (
      SELECT messages.*, messages.rowid AS _rid FROM messages
      WHERE conversation_id = ? ORDER BY messages.rowid DESC LIMIT ?
    ) sub ORDER BY _rid ASC
  `).all(convId, limit);
}

// Cursor-based pagination: messages before a given message id.
// Use rowid for comparison — message.id is random hex so `id < ?` is meaningless.
export function getMessagesBefore(convId: string, beforeId: string, limit = 20) {
  return db.prepare(`
    SELECT * FROM (
      SELECT messages.*, messages.rowid AS _rid FROM messages
      WHERE conversation_id = ?
        AND messages.rowid < (SELECT rowid FROM messages WHERE id = ?)
      ORDER BY messages.rowid DESC LIMIT ?
    ) sub ORDER BY _rid ASC
  `).all(convId, beforeId, limit);
}
