const mysql = require("mysql2/promise");
const { Client } = require("ssh2");
const { Pool } = require('pg');
const dotenv = require("dotenv");
const path = require("path");

// Helper function to setup SSH tunnel
async function setupSshTunnel(sshConfig) {
  return new Promise((resolve, reject) => {
    const ssh = new Client();

    ssh.on('error', (err) => {
      console.error('SSH connection error:', err);
    });

    ssh.on('end', () => {
      console.log('SSH connection ended normally');
    });

    ssh.on('close', () => {
      console.log('SSH connection closed');
    });

    ssh
      .on("ready", () => {
        ssh.forwardOut(
          "127.0.0.1",
          0,
          sshConfig.prodDbConfig.host,
          sshConfig.prodDbConfig.port,
          async (err, stream) => {
            if (err) reject(err);
            resolve({ ssh, stream });
          }
        );
      })
      .connect(sshConfig.ssh);
  });
}

// Helper function to setup database connections
async function setupConnections(sshConfig) {
  const tunnel = await setupSshTunnel(sshConfig);
  
  // Setup MySQL connection for production
  const prodConnection = await mysql.createConnection({
    ...sshConfig.prodDbConfig,
    stream: tunnel.stream,
  });

  // Detect MySQL server timezone and calculate correction for the driver timezone mismatch.
  // The mysql2 driver is configured with timezone: '-05:00' (EST), but the MySQL server
  // may be in a different timezone (e.g., America/Chicago = CST/CDT). When the driver
  // formats a JS Date as EST and MySQL interprets it in its own timezone, DATETIME
  // comparisons can be off. This correction adjusts Date objects before they're passed
  // to MySQL queries so the formatted string matches the server's local time.
  const [[{ utcDiffSec }]] = await prodConnection.query(
    "SELECT TIMESTAMPDIFF(SECOND, NOW(), UTC_TIMESTAMP()) as utcDiffSec"
  );
  const mysqlOffsetMs = -utcDiffSec * 1000;  // MySQL UTC offset in ms (e.g., -21600000 for CST)
  // Driver offset from the actual config (e.g. '-06:00' in winter, '-05:00'
  // in summer — set dynamically in import-from-prod.js).
  const tzMatch = /^([+-])(\d{2}):(\d{2})$/.exec(sshConfig.prodDbConfig.timezone || '');
  const driverOffsetMs = tzMatch
    ? (tzMatch[1] === '-' ? -1 : 1) * (Number(tzMatch[2]) * 3600 + Number(tzMatch[3]) * 60) * 1000
    : -5 * 3600 * 1000;
  const tzCorrectionMs = driverOffsetMs - mysqlOffsetMs;
  // CST (winter): -18000000 - (-21600000) = +3600000 (1 hour correction needed)
  // CDT (summer): -18000000 - (-18000000) = 0 (no correction needed)

  if (tzCorrectionMs !== 0) {
    console.log(`MySQL timezone correction: ${tzCorrectionMs / 1000}s (server offset: ${utcDiffSec}s from UTC)`);
  }

  /**
   * Adjusts a Date/timestamp for the mysql2 driver timezone mismatch before
   * passing it as a query parameter to MySQL. This ensures that the string
   * mysql2 generates matches the timezone that DATETIME values are stored in.
   */
  function adjustDateForMySQL(date) {
    if (!date || tzCorrectionMs === 0) return date;
    const d = date instanceof Date ? date : new Date(date);
    return new Date(d.getTime() - tzCorrectionMs);
  }
  prodConnection.adjustDateForMySQL = adjustDateForMySQL;

  // Setup PostgreSQL connection pool for local.
  // Pin the session timezone to business time (see docs/TIME.md) so import
  // writes and any CURRENT_DATE/NOW()::date logic mean Chicago dates.
  const localPool = new Pool({
    options: '-c TimeZone=America/Chicago',
    ...sshConfig.localDbConfig,
  });

  // Test the PostgreSQL connection
  try {
    const client = await localPool.connect();
    await client.query('SELECT NOW()');
    client.release();
    console.log('PostgreSQL connection successful');
  } catch (err) {
    console.error('PostgreSQL connection error:', err);
    throw err;
  }

  // Create a wrapper for the PostgreSQL pool to match MySQL interface
  const localConnection = {
    _client: null,
    _transactionActive: false,

    query: async (text, params) => {
      // If we're not in a transaction, use the pool directly
      if (!localConnection._transactionActive) {
        const client = await localPool.connect();
        try {
          const result = await client.query(text, params);
          return [result];
        } finally {
          client.release();
        }
      }
      
      // If we're in a transaction, use the dedicated client
      if (!localConnection._client) {
        throw new Error('No active transaction client');
      }
      const result = await localConnection._client.query(text, params);
      return [result];
    },

    beginTransaction: async () => {
      if (localConnection._transactionActive) {
        throw new Error('Transaction already active');
      }
      localConnection._client = await localPool.connect();
      await localConnection._client.query('BEGIN');
      localConnection._transactionActive = true;
    },

    commit: async () => {
      if (!localConnection._transactionActive) {
        throw new Error('No active transaction to commit');
      }
      await localConnection._client.query('COMMIT');
      localConnection._client.release();
      localConnection._client = null;
      localConnection._transactionActive = false;
    },

    rollback: async () => {
      if (!localConnection._transactionActive) {
        throw new Error('No active transaction to rollback');
      }
      await localConnection._client.query('ROLLBACK');
      localConnection._client.release();
      localConnection._client = null;
      localConnection._transactionActive = false;
    },

    end: async () => {
      if (localConnection._client) {
        localConnection._client.release();
        localConnection._client = null;
      }
      await localPool.end();
    }
  };

  return { prodConnection, localConnection, tunnel };
}

// Helper function to close connections
async function closeConnections(connections) {
  const { ssh, prodConnection, localConnection } = connections;
  
  try {
    if (prodConnection) await prodConnection.end();
    if (localConnection) await localConnection.end();
    
    // Wait a bit for any pending data to be written before closing SSH
    await new Promise(resolve => setTimeout(resolve, 100));
    
    if (ssh) {
      ssh.on('close', () => {
        console.log('SSH connection closed cleanly');
      });
      ssh.end();
    }
  } catch (err) {
    console.error('Error during cleanup:', err);
  }
}

module.exports = {
  setupConnections,
  closeConnections
}; 