import fs from 'fs';
import path from 'path';
import Database from 'better-sqlite3';
import logger from '../../services/logger.js';
import { config } from '../../utils.js';

/**
 * SqliteConnection
 * A small, high-performance wrapper around better-sqlite3 that provides a
 * singleton connection, sensible PRAGMA tuning, and helper methods. This
 * module is safe to import and reuse.
 *
 * Performance notes:
 * - journal_mode = WAL: allows concurrent readers with a single writer and
 *   yields better performance for server apps.
 * - synchronous = NORMAL: trades a bit of durability for significant speed
 *   while still being safe in most environments.
 * - cache_size = -64000: ~64MB page cache (negative value sets KB) to improve
 *   query performance for frequent reads.
 * - foreign_keys = ON: ensure referential integrity is enforced.
 * - optimize: runs SQLite's auto-analysis and purges internal caches. It is
 *   cheap; we call it at startup and before process exit. You can also call
 *   optimize() manually after large schema changes or bulk operations.
 */
class SqliteConnection {
  static #db = null;

  /**
   * Returns a singleton instance of better-sqlite3 Database.
   * Respects env var SQLITE_DB_PATH and defaults to db/listings.db.
   */
  static getConnection() {
    if (this.#db) return this.#db;

    // Interpret config.sqlitepath as a directory relative to project root when it starts with '/'
    const cfg = typeof config === 'object' && config ? config.sqlitepath : undefined;
    const rawDir = cfg && cfg.length > 0 ? cfg : '/db';
    const relDir = rawDir.startsWith('/') ? rawDir.slice(1) : rawDir;
    const absDir = path.isAbsolute(relDir) ? relDir : path.join(process.cwd(), relDir);
    const dbPath = path.join(absDir, 'listings.db');

    // Ensure directory exists
    const dir = path.dirname(dbPath);
    if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

    // Open the database synchronously (better-sqlite3 is sync and very fast)
    this.#db = new Database(dbPath, { verbose: undefined });

    // Apply high-performance PRAGMA's
    try {
      this.#db.pragma('journal_mode = WAL');
      this.#db.pragma('synchronous = NORMAL');
      this.#db.pragma('cache_size = -64000');
      this.#db.pragma('foreign_keys = ON');
      this.#db.pragma('optimize');
    } catch (e) {
      logger.warn('Failed to apply one or more PRAGMAs:', e.message);
    }

    // Run optimize on exit to persist analysis and cleanup internal caches.
    process.once('beforeExit', () => {
      try {
        this.#db?.pragma('optimize');
      } catch (e) {
        logger.debug('PRAGMA optimize on exit failed:', e.message);
      }
    });

    return this.#db;
  }

  /**
   * Execute a write statement (INSERT/UPDATE/DELETE/DDL). Returns better-sqlite3 run info.
   */
  static execute(sql, params = {}) {
    const db = this.getConnection();
    return db.prepare(sql).run(params);
  }

  /**
   * Execute a query and returns all rows.
   */
  static query(sql, params = {}) {
    const db = this.getConnection();
    return db.prepare(sql).all(params);
  }

  /**
   * Check whether a table exists.
   */
  static tableExists(tableName) {
    const db = this.getConnection();
    const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(tableName);
    return !!row;
  }

  /**
   * Run the given callback inside a transaction. The callback receives the Database instance.
   * If the callback throws, the transaction is rolled back and the error re-thrown.
   */
  static withTransaction(callback) {
    const db = this.getConnection();
    const trx = db.transaction((cb) => cb(db));
    return trx(callback);
  }

  /**
   * Run SQLite PRAGMA optimize. See https://sqlite.org/pragma.html#pragma_optimize
   *
   * Explanation: PRAGMA optimize triggers internal housekeeping, such as
   * recomputing query planner statistics (similar to ANALYZE) when appropriate
   * and purging unused pages from caches. It is inexpensive and can improve
   * performance after schema changes or heavy write activity.
   */
  static optimize() {
    const db = this.getConnection();
    try {
      db.pragma('optimize');
    } catch (e) {
      logger.warn('PRAGMA optimize failed:', e.message);
    }
  }

  /**
   * Close the database connection. Typically not needed for long-running apps.
   */
  static close() {
    if (this.#db) {
      try {
        this.#db.pragma('optimize');
      } catch (e) {
        logger.debug('PRAGMA optimize before close failed:', e.message);
      }
      this.#db.close();
      this.#db = null;
    }
  }
}

export default SqliteConnection;
