import { Client } from 'ssh2';
import mysql from 'mysql2/promise';
import fs from 'node:fs';

const connectionCache = {
  ssh: null,
  dbConnection: null,
  lastUsed: 0,
  isConnecting: false,
  connectionPromise: null,
  expirationTime: 5 * 60 * 1000,
  queryCache: new Map(),
  cacheDuration: {
    'field-options': 30 * 60 * 1000,
    'product-lines': 10 * 60 * 1000,
    'sublines': 10 * 60 * 1000,
    'taxonomy': 30 * 60 * 1000,
    'default': 60 * 1000,
  },
};

export async function getDbConnection() {
  const now = Date.now();
  const needsRefresh = !connectionCache.ssh
    || !connectionCache.dbConnection
    || (now - connectionCache.lastUsed > connectionCache.expirationTime);

  if (!needsRefresh) {
    connectionCache.lastUsed = now;
    return { ssh: connectionCache.ssh, connection: connectionCache.dbConnection };
  }

  if (connectionCache.isConnecting && connectionCache.connectionPromise) {
    try {
      await connectionCache.connectionPromise;
      return { ssh: connectionCache.ssh, connection: connectionCache.dbConnection };
    } catch (error) {
      console.error('Error waiting for existing connection:', error);
    }
  }

  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); }
  }

  connectionCache.isConnecting = true;
  connectionCache.connectionPromise = setupSshTunnel().then((tunnel) => {
    const { ssh, stream, dbConfig } = tunnel;
    return mysql.createConnection({ ...dbConfig, stream }).then((connection) => {
      connectionCache.ssh = ssh;
      connectionCache.dbConnection = connection;
      connectionCache.lastUsed = Date.now();
      connectionCache.isConnecting = false;
      return { ssh, connection };
    });
  }).catch((error) => {
    connectionCache.isConnecting = false;
    throw error;
  });

  return connectionCache.connectionPromise;
}

export async function getCachedQuery(cacheKey, queryType, queryFn) {
  const cacheDuration = connectionCache.cacheDuration[queryType] || connectionCache.cacheDuration.default;
  const cachedResult = connectionCache.queryCache.get(cacheKey);
  const now = Date.now();

  if (cachedResult && (now - cachedResult.timestamp < cacheDuration)) {
    return cachedResult.data;
  }

  const result = await queryFn();
  connectionCache.queryCache.set(cacheKey, { data: result, timestamp: now });
  return result;
}

async function setupSshTunnel() {
  const sshConfig = {
    host: process.env.PROD_SSH_HOST,
    port: Number(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: Number(process.env.PROD_DB_PORT) || 3306,
    // DATETIME columns store Central wall-clock literals — return them as
    // strings; parse with shared/business-time parseMySql() if instants are
    // ever needed (see docs/TIME.md).
    dateStrings: true,
  };

  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);
        else resolve({ ssh, stream, dbConfig });
      });
    }).connect(sshConfig);
  });
}

export function clearQueryCache(cacheKey) {
  if (cacheKey) connectionCache.queryCache.delete(cacheKey);
  else connectionCache.queryCache.clear();
}

export async function closeAllConnections() {
  if (connectionCache.dbConnection) {
    try { await connectionCache.dbConnection.end(); }
    catch (error) { console.error('Error closing database connection:', error); }
    connectionCache.dbConnection = null;
  }
  if (connectionCache.ssh) {
    try { connectionCache.ssh.end(); }
    catch (error) { console.error('Error closing SSH connection:', error); }
    connectionCache.ssh = null;
  }
  connectionCache.lastUsed = 0;
  connectionCache.isConnecting = false;
  connectionCache.connectionPromise = null;
}
