/**
 * Database Connection and Query Utilities
 * Provides PostgreSQL database connection and query execution functions
 */

import pkg from 'pg';
const { Pool } = pkg;
import { fail } from '@zen/core/shared/logger';

/**
 * Opaque error type thrown by all database operations.
 * Exposes only a generic message and the pg error code (e.g. '23505') so
 * callers can branch on well-known codes without receiving internal details
 * such as table names, constraint names, or query fragments.
 */
export class DatabaseError extends Error {
  /** @param {string} message - Safe, generic message */
  /** @param {string|undefined} code - PostgreSQL error code (SQLSTATE), if any */
  constructor(message, code) {
    super(message);
    this.name = 'DatabaseError';
    if (code !== undefined) this.code = code;
  }
}

let pool = null;

function resolveDatabaseUrl() {
  const isDev = process.env.NODE_ENV === 'development';
  if (isDev && process.env.ZEN_DATABASE_URL_DEV) {
    return process.env.ZEN_DATABASE_URL_DEV;
  }
  return process.env.ZEN_DATABASE_URL;
}

/**
 * Get or create a database connection pool
 * @returns {Pool} PostgreSQL connection pool
 */
function getPool() {
  if (!pool) {
    const databaseUrl = resolveDatabaseUrl();

    if (!databaseUrl) {
      throw new Error(
        process.env.NODE_ENV === 'development'
          ? 'ZEN_DATABASE_URL or ZEN_DATABASE_URL_DEV must be defined in environment variables'
          : 'ZEN_DATABASE_URL is not defined in environment variables'
      );
    }

    pool = new Pool({
      connectionString: databaseUrl,
      // TLS policy:
      //   production              → full TLS with server certificate verification
      //   all other environments  → TLS encryption on, certificate verification off
      //                             (prevents eavesdropping; allows self-signed certs in dev/ci)
      //   ZEN_DB_SSL_DISABLED=true → opt-out of TLS entirely (local loopback only)
      ssl: process.env.NODE_ENV === 'production'
        ? { rejectUnauthorized: true }
        : (process.env.ZEN_DB_SSL_DISABLED === 'true'
            ? false
            : { rejectUnauthorized: false }),
      max: 20, // Maximum number of clients in the pool
      idleTimeoutMillis: 30000, // Close idle clients after 30 seconds
      connectionTimeoutMillis: 2000, // Return an error after 2 seconds if connection could not be established
    });

    // Handle pool errors
    pool.on('error', (err) => {
      fail(`DB idle client error: ${err.message}`);
    });
  }

  return pool;
}

/**
 * Execute a SQL query
 * @param {string} sql - SQL query string
 * @param {Array} params - Query parameters (optional)
 * @returns {Promise<Object>} Query result
 */
async function query(sql, params = []) {
  const dbPool = getPool(); // renamed — avoids shadowing module-level `pool`

  try {
    const result = await dbPool.query(sql, params);
    return result;
  } catch (error) {
    fail(`DB query error: ${error.message}`);
    throw new DatabaseError('A database error occurred', error.code);
  }
}

/**
 * Execute a query and return the first row
 * @param {string} sql - SQL query string
 * @param {Array} params - Query parameters (optional)
 * @returns {Promise<Object|null>} First row or null if no results
 */
async function queryOne(sql, params = []) {
  const result = await query(sql, params);
  return result.rows.length > 0 ? result.rows[0] : null;
}

/**
 * Execute a query and return all rows
 * @param {string} sql - SQL query string
 * @param {Array} params - Query parameters (optional)
 * @returns {Promise<Array>} Array of rows
 */
async function queryAll(sql, params = []) {
  const result = await query(sql, params);
  return result.rows;
}

/**
 * Execute multiple queries in a transaction
 * @param {Function} callback - Async function that receives a client and executes queries
 * @returns {Promise<any>} Result of the callback function
 */
async function transaction(callback) {
  const client = await getPool().connect();
  
  try {
    await client.query('BEGIN');
    const result = await callback(client);
    await client.query('COMMIT');
    return result;
  } catch (error) {
    await client.query('ROLLBACK');
    fail(`DB transaction error: ${error.message}`);
    throw new DatabaseError('A database transaction error occurred', error.code);
  } finally {
    client.release();
  }
}

/**
 * Close the database connection pool
 * @returns {Promise<void>}
 */
async function closePool() {
  if (pool) {
    await pool.end();
    pool = null;
  }
}

/**
 * Test database connection
 * @returns {Promise<boolean>} True if connection successful
 */
async function testConnection() {
  try {
    await query('SELECT NOW()');
    return true;
  } catch (error) {
    fail(`DB connection test failed: ${error.message}`);
    return false;
  }
}

/**
 * Check if a table exists in the database
 * @param {string} tableName - Name of the table to check
 * @returns {Promise<boolean>} True if table exists, false otherwise
 */
async function tableExists(tableName) {
  const result = await query(
    `SELECT EXISTS (
      SELECT FROM information_schema.tables
      WHERE table_schema = 'public'
      AND table_name = $1
    )`,
    [tableName]
  );
  return result.rows[0].exists;
}

export {
  query,
  queryOne,
  queryAll,
  transaction,
  getPool,
  closePool,
  testConnection,
  tableExists
};

