/**
 * Audit Table Runner
 *
 * Mengeksekusi DDL audit table ke database yang dituju.
 * Bersifat idempotent (table existing tidak menyebabkan error).
 *
 * Failure mode:
 * - Connection gagal (timeout/auth) → warning + dry-run mode (return status,
 *   tidak throw). Generator boleh lanjut karena migration bisa dijalankan
 *   manual via file SQL yang ditulis.
 * - DDL gagal setelah connection sukses → throw, generator abort.
 *
 * @module lib/migration/audit-table-runner
 */

const fs = require('fs');
const path = require('path');
const { generateAuditTableMigration } = require('./audit-table-generator');

/**
 * Jalankan migration audit table.
 *
 * @param {string} tableName - Nama tabel sumber
 * @param {string} dialect - 'postgres' | 'mysql' | 'oracle'
 * @param {Object|null} connectionInfo - Connection info { host, port, user, password, database, connectString? }.
 *                                       Jika null → dry-run mode (file SQL tetap ditulis).
 * @param {Object} [options]
 * @param {string} [options.projectRoot=process.cwd()] - Root project untuk menulis migrations/audit/
 * @param {Object} [options.logger=console] - Logger dengan method info/warn/error
 * @returns {Promise<{status: string, sqlPath: string, auditTable: string}>}
 *          status: 'created' | 'exists' | 'dry-run' | 'connection-failed'
 */
async function runAuditMigration(tableName, dialect, connectionInfo, options = {}) {
  const projectRoot = options.projectRoot || process.cwd();
  const logger = options.logger || console;
  const auditTable = `${tableName}_audit`;
  const sql = generateAuditTableMigration(tableName, dialect);

  const sqlPath = writeMigrationFile(projectRoot, auditTable, sql);
  logger.info(`[restforge] audit migration written: ${path.relative(projectRoot, sqlPath)}`);

  if (!connectionInfo) {
    logger.warn(`[restforge] audit migration skipped — database connection not configured`);
    return { status: 'dry-run', sqlPath, auditTable };
  }

  let connection;
  try {
    connection = await connectToDatabase(dialect, connectionInfo);
  } catch (err) {
    logger.warn(`[restforge] audit migration skipped — connection failed: ${err.message}`);
    return { status: 'connection-failed', sqlPath, auditTable };
  }

  try {
    const existed = await auditTableExists(connection, dialect, auditTable, connectionInfo);
    await executeMigration(connection, dialect, sql);
    logger.info(existed
      ? `[restforge] audit table exists: ${auditTable}`
      : `[restforge] audit table created: ${auditTable}`);
    return { status: existed ? 'exists' : 'created', sqlPath, auditTable };
  } catch (err) {
    logger.error(`[restforge] audit migration failed: ${err.message}`);
    throw err;
  } finally {
    await closeConnection(connection, dialect);
  }
}

function writeMigrationFile(projectRoot, auditTable, sql) {
  const migrationsDir = path.join(projectRoot, 'migrations', 'audit');
  fs.mkdirSync(migrationsDir, { recursive: true });
  const sqlPath = path.join(migrationsDir, `${auditTable}.sql`);
  fs.writeFileSync(sqlPath, sql + '\n', 'utf8');
  return sqlPath;
}

async function connectToDatabase(dialect, connectionInfo) {
  if (dialect === 'postgres') {
    const { Client } = require('pg');
    const client = new Client({
      host: connectionInfo.host,
      port: connectionInfo.port,
      user: connectionInfo.user,
      password: connectionInfo.password,
      database: connectionInfo.database
    });
    await client.connect();
    return client;
  }

  if (dialect === 'mysql') {
    const mysql = require('mysql2/promise');
    return await mysql.createConnection({
      host: connectionInfo.host,
      port: connectionInfo.port,
      user: connectionInfo.user,
      password: connectionInfo.password,
      database: connectionInfo.database,
      multipleStatements: true
    });
  }

  if (dialect === 'oracle') {
    const oracledb = require('oracledb');
    return await oracledb.getConnection({
      user: connectionInfo.user,
      password: connectionInfo.password,
      connectString: connectionInfo.connectString
        || `${connectionInfo.host}:${connectionInfo.port}/${connectionInfo.database}`
    });
  }

  throw new Error(`connectToDatabase: unsupported dialect '${dialect}'`);
}

async function closeConnection(connection, dialect) {
  if (!connection) return;
  try {
    if (dialect === 'oracle') {
      await connection.close();
    } else {
      await connection.end();
    }
  } catch (_err) {
    // ignore close error — primary action sudah selesai
  }
}

async function auditTableExists(connection, dialect, auditTable, connectionInfo) {
  if (dialect === 'postgres') {
    const { rows } = await connection.query(
      `SELECT 1 FROM information_schema.tables
       WHERE table_schema = current_schema() AND table_name = $1
       LIMIT 1`,
      [auditTable]
    );
    return rows.length > 0;
  }

  if (dialect === 'mysql') {
    const [rows] = await connection.query(
      `SELECT 1 FROM information_schema.tables
       WHERE table_schema = ? AND table_name = ?
       LIMIT 1`,
      [connectionInfo.database, auditTable]
    );
    return rows.length > 0;
  }

  if (dialect === 'oracle') {
    const result = await connection.execute(
      `SELECT 1 FROM user_tables WHERE table_name = :name`,
      { name: auditTable.toUpperCase() }
    );
    return result.rows && result.rows.length > 0;
  }

  return false;
}

async function executeMigration(connection, dialect, sql) {
  if (dialect === 'postgres') {
    // pg client mendukung multiple statement dalam satu query() tanpa params
    await connection.query(sql);
    return;
  }

  if (dialect === 'mysql') {
    // mysql2 dengan multipleStatements: true
    await connection.query(sql);
    return;
  }

  if (dialect === 'oracle') {
    // Oracle PL/SQL anonim dipisahkan oleh '/' di line tersendiri.
    // oracledb.execute() menerima satu statement per call.
    const blocks = splitOraclePlsqlBlocks(sql);
    for (const block of blocks) {
      const trimmed = block.trim();
      if (trimmed === '') continue;
      await connection.execute(trimmed);
    }
    return;
  }

  throw new Error(`executeMigration: unsupported dialect '${dialect}'`);
}

function splitOraclePlsqlBlocks(sql) {
  // Pisahkan blok PL/SQL berdasarkan baris yang isinya hanya '/'
  const lines = sql.split(/\r?\n/);
  const blocks = [];
  let current = [];
  for (const line of lines) {
    if (line.trim() === '/') {
      if (current.length > 0) {
        blocks.push(current.join('\n'));
        current = [];
      }
    } else {
      current.push(line);
    }
  }
  if (current.length > 0 && current.join('').trim() !== '') {
    blocks.push(current.join('\n'));
  }
  return blocks;
}

module.exports = {
  runAuditMigration
};
