import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
import { app } from 'electron';
import fs from 'node:fs';
import path from 'node:path';
import * as schema from './schema';

/** Resolved path to the SQLite database file. Set once in initDb(). */
let _dbPath: string | null = null;

/** Raw better-sqlite3 instance (needed for .backup() API). */
let _rawSqlite: Database.Database | null = null;

type DbInstance = ReturnType<typeof drizzle<typeof schema>>;

let dbInstance: DbInstance | null = null;

/**
 * Resolve the migrations folder location.
 *
 * - Packaged: shipped via electron-forge `extraResource` → `<resourcesPath>/migrations`.
 * - Dev: lives in the source tree at `<appPath>/src/main/db/migrations`. We do NOT
 *   resolve from `__dirname` because Vite bundles `src/main/**` into a single
 *   `.vite/build/main.js` and the migrations folder is not copied next to it.
 */
function resolveMigrationsFolder(): string {
  if (app.isPackaged) {
    return path.join(process.resourcesPath, 'migrations');
  }
  return path.join(app.getAppPath(), 'src', 'main', 'db', 'migrations');
}

/**
 * One-time bootstrap for DBs created by the legacy hand-rolled MIGRATION_SQL.
 *
 * Pre-Drizzle-migrator era, schema was managed by ad-hoc CREATE TABLE IF NOT EXISTS
 * + try/catch ALTER TABLE. Those DBs have all the tables from migrations 0000-0003
 * but no `__drizzle_migrations` ledger. If we just call migrate(), it will try to
 * re-run 0000 and crash on duplicate table.
 *
 * Strategy: if the DB looks pre-existing (has a `tasks` table) but no migrations
 * ledger, create the ledger and mark all migrations EXCEPT the latest as applied.
 * The migrator will then only run the latest one (0004 — adds `estimate` column +
 * `task_attachments` table — both genuinely missing from legacy DBs).
 */
function bootstrapMigrationsLedger(sqlite: Database.Database, migrationsFolder: string): void {
  const hasLedger = sqlite
    .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='__drizzle_migrations'")
    .get();
  if (hasLedger) return;

  const hasTasks = sqlite
    .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='tasks'")
    .get();
  if (!hasTasks) return; // fresh DB — let the migrator create everything from scratch

  // Legacy DB detected. Build the ledger Drizzle expects.
  // Schema must match drizzle-orm/sqlite-core/dialect.js migrate():
  //   id SERIAL PRIMARY KEY, hash text NOT NULL, created_at numeric
  sqlite.exec(`
    CREATE TABLE __drizzle_migrations (
      id SERIAL PRIMARY KEY,
      hash text NOT NULL,
      created_at numeric
    );
  `);

  const journalPath = path.join(migrationsFolder, 'meta', '_journal.json');
  const journal = JSON.parse(fs.readFileSync(journalPath, 'utf8')) as {
    entries: { idx: number; tag: string; when: number }[];
  };

  // Mark everything except the latest entry as applied.
  // Drizzle's migrator filters by `lastDbMigration.created_at < migration.folderMillis`,
  // so seeding the second-to-last entry's `when` is sufficient.
  const toMark = journal.entries.slice(0, -1);
  if (toMark.length === 0) return;

  const insert = sqlite.prepare(
    'INSERT INTO __drizzle_migrations (hash, created_at) VALUES (?, ?)',
  );
  for (const entry of toMark) {
    // Hash value is opaque to the migrator — only created_at matters for the cutoff.
    // Use the tag for traceability.
    insert.run(entry.tag, entry.when);
  }
}

export function initDb(): DbInstance {
  const userDataPath = app.getPath('userData');
  _dbPath = path.join(userDataPath, 'adiuvai.db');

  const sqlite = new Database(_dbPath);
  _rawSqlite = sqlite;

  // Enable WAL mode for better concurrent read performance
  sqlite.pragma('journal_mode = WAL');
  sqlite.pragma('synchronous = NORMAL');

  const migrationsFolder = resolveMigrationsFolder();
  bootstrapMigrationsLedger(sqlite, migrationsFolder);

  dbInstance = drizzle(sqlite, { schema });
  migrate(dbInstance, { migrationsFolder });

  return dbInstance;
}

export function getDb(): DbInstance {
  if (!dbInstance) {
    throw new Error('Database not initialized. Call initDb() first.');
  }
  return dbInstance;
}

/** Returns the absolute path to the active SQLite database file. */
export function getDbPath(): string {
  if (!_dbPath) throw new Error('Database not initialized.');
  return _dbPath;
}

/**
 * Returns the raw better-sqlite3 Database instance.
 * Used by BackupManager for the `.backup()` API.
 */
export function getRawSqlite(): Database.Database {
  if (!_rawSqlite) throw new Error('Database not initialized.');
  return _rawSqlite;
}

/**
 * Closes the database connection and clears all module-level references.
 * Called by BackupManager before atomically replacing the DB file.
 * After calling this, you must call `initDb()` again to re-open.
 */
export function closeDb(): void {
  if (_rawSqlite) {
    try { _rawSqlite.close(); } catch { /* ignore */ }
    _rawSqlite = null;
  }
  dbInstance = null;
}
