const { Client } = require('ssh2');
const mysql = require('mysql2/promise');
const fs = require('fs');

// Connection pooling and cache configuration
const connectionCache = {
  ssh: null,
  dbConnection: null,
  lastUsed: 0,
  isConnecting: false,
  connectionPromise: null,
  // Cache expiration time in milliseconds (5 minutes)
  expirationTime: 5 * 60 * 1000,
  // Cache for query results (key: query string, value: {data, timestamp})
  queryCache: new Map(),
  // Cache duration for different query types in milliseconds
  cacheDuration: {
    'field-options': 30 * 60 * 1000, // 30 minutes for field options
    'product-lines': 10 * 60 * 1000, // 10 minutes for product lines
    'sublines': 10 * 60 * 1000,      // 10 minutes for sublines
    'taxonomy': 30 * 60 * 1000,      // 30 minutes for taxonomy data
    'default': 60 * 1000             // 1 minute default
  }
};

/**
 * Get a database connection with connection pooling
 * @returns {Promise<{ssh: object, connection: object}>} The SSH and database connection
 */
async function getDbConnection() {
  const now = Date.now();
  
  // Check if we need to refresh the connection due to inactivity
  const needsRefresh = !connectionCache.ssh || 
                       !connectionCache.dbConnection || 
                       (now - connectionCache.lastUsed > connectionCache.expirationTime);
  
  // If connection is still valid, update last used time and return existing connection
  if (!needsRefresh) {
    connectionCache.lastUsed = now;
    return {
      ssh: connectionCache.ssh,
      connection: connectionCache.dbConnection
    };
  }
  
  // If another request is already establishing a connection, wait for that promise
  if (connectionCache.isConnecting && connectionCache.connectionPromise) {
    try {
      await connectionCache.connectionPromise;
      return {
        ssh: connectionCache.ssh,
        connection: connectionCache.dbConnection
      };
    } catch (error) {
      // If that connection attempt failed, we'll try again below
      console.error('Error waiting for existing connection:', error);
    }
  }
  
  // Close existing connections if they exist
  if (connectionCache.dbConnection) {
    try {
      await connectionCache.dbConnection.end();
    } catch (error) {
      console.error('Error closing existing database connection:', error);
    }
  }
  
  if (connectionCache.ssh) {
    try {
      connectionCache.ssh.end();
    } catch (error) {
      console.error('Error closing existing SSH connection:', error);
    }
  }
  
  // Mark that we're establishing a new connection
  connectionCache.isConnecting = true;
  
  // Create a new promise for this connection attempt
  connectionCache.connectionPromise = setupSshTunnel().then(tunnel => {
    const { ssh, stream, dbConfig } = tunnel;
    
    return mysql.createConnection({
      ...dbConfig,
      stream
    }).then(connection => {
      // Store the new connections
      connectionCache.ssh = ssh;
      connectionCache.dbConnection = connection;
      connectionCache.lastUsed = Date.now();
      connectionCache.isConnecting = false;
      
      return {
        ssh,
        connection
      };
    });
  }).catch(error => {
    connectionCache.isConnecting = false;
    throw error;
  });
  
  // Wait for the connection to be established
  return connectionCache.connectionPromise;
}

/**
 * Get cached query results or execute query if not cached
 * @param {string} cacheKey - Unique key to identify the query
 * @param {string} queryType - Type of query (field-options, product-lines, etc.)
 * @param {Function} queryFn - Function to execute if cache miss
 * @returns {Promise<any>} The query result
 */
async function getCachedQuery(cacheKey, queryType, queryFn) {
  // Get cache duration based on query type
  const cacheDuration = connectionCache.cacheDuration[queryType] || connectionCache.cacheDuration.default;
  
  // Check if we have a valid cached result
  const cachedResult = connectionCache.queryCache.get(cacheKey);
  const now = Date.now();
  
  if (cachedResult && (now - cachedResult.timestamp < cacheDuration)) {
    console.log(`Cache hit for ${queryType} query: ${cacheKey}`);
    return cachedResult.data;
  }
  
  // No valid cache found, execute the query
  console.log(`Cache miss for ${queryType} query: ${cacheKey}`);
  const result = await queryFn();
  
  // Cache the result
  connectionCache.queryCache.set(cacheKey, {
    data: result,
    timestamp: now
  });
  
  return result;
}

/**
 * Setup SSH tunnel to production database
 * @private - Should only be used by getDbConnection
 * @returns {Promise<{ssh: object, stream: object, dbConfig: object}>}
 */
async function setupSshTunnel() {
  const sshConfig = {
    host: process.env.PROD_SSH_HOST,
    port: process.env.PROD_SSH_PORT || 22,
    username: process.env.PROD_SSH_USER,
    privateKey: process.env.PROD_SSH_KEY_PATH
      ? fs.readFileSync(process.env.PROD_SSH_KEY_PATH)
      : undefined,
    compress: true
  };

  const dbConfig = {
    host: process.env.PROD_DB_HOST || 'localhost',
    user: process.env.PROD_DB_USER,
    password: process.env.PROD_DB_PASSWORD,
    database: process.env.PROD_DB_NAME,
    port: process.env.PROD_DB_PORT || 3306,
    timezone: 'Z'
  };

  return new Promise((resolve, reject) => {
    const ssh = new Client();

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

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

/**
 * Clear cached query results
 * @param {string} [cacheKey] - Specific cache key to clear (clears all if not provided)
 */
function clearQueryCache(cacheKey) {
  if (cacheKey) {
    connectionCache.queryCache.delete(cacheKey);
    console.log(`Cleared cache for key: ${cacheKey}`);
  } else {
    connectionCache.queryCache.clear();
    console.log('Cleared all query cache');
  }
}

/**
 * Force close all active connections
 * Useful for server shutdown or manual connection reset
 */
async function closeAllConnections() {
  if (connectionCache.dbConnection) {
    try {
      await connectionCache.dbConnection.end();
      console.log('Closed database connection');
    } catch (error) {
      console.error('Error closing database connection:', error);
    }
    connectionCache.dbConnection = null;
  }
  
  if (connectionCache.ssh) {
    try {
      connectionCache.ssh.end();
      console.log('Closed SSH connection');
    } catch (error) {
      console.error('Error closing SSH connection:', error);
    }
    connectionCache.ssh = null;
  }
  
  connectionCache.lastUsed = 0;
  connectionCache.isConnecting = false;
  connectionCache.connectionPromise = null;
}

module.exports = {
  getDbConnection,
  getCachedQuery,
  clearQueryCache,
  closeAllConnections
}; 