/**
 * src/bot/mercuryGuard.js
 *
 * Prevents the Webex Mercury WebSocket plugin from crashing the Node process.
 *
 * Root cause (confirmed from production stack trace 2026-08-04):
 *   @webex/internal-plugin-mercury socket-base.js onmessage → _acknowledge → send()
 *   rejects with Error('INVALID_STATE_ERROR') when readyState !== OPEN (1).
 *   That promise is never caught inside the plugin, so it becomes an
 *   unhandledRejection / uncaughtException and terminates Node.
 *
 * This module:
 *   1. Catches Mercury INVALID_STATE_ERROR at the process level (no crash).
 *   2. Debounces a Framework.restart() to rebuild the websocket listeners.
 *   3. Hooks mercury offline/online events for proactive recovery.
 *   4. Periodically logs connection health.
 */

import { logger } from '../utils/logger.js';

let guardInstalled = false;
let restartInFlight = false;
let restartTimer = null;

/** True when err is the Mercury socket ack-on-closed-socket failure. */
export function isMercuryInvalidStateError(err) {
  if (!err) return false;
  const msg = String(err.message || err);
  if (msg !== 'INVALID_STATE_ERROR') return false;
  const stack = String(err.stack || '');
  return (
    stack.includes('mercury') ||
    stack.includes('socket-base') ||
    stack.includes('internal-plugin-mercury') ||
    stack.includes('plugin-mercury')
  );
}

/** Read mercury connection snapshot without throwing. */
export function getMercuryState(Framework) {
  try {
    const mercury = Framework?.webex?.internal?.mercury;
    const socket = mercury?.socket;
    return {
      connected: Boolean(mercury?.connected),
      connecting: Boolean(mercury?.connecting),
      readyState: socket?.readyState ?? null,
    };
  } catch {
    return null;
  }
}

function scheduleFrameworkRestart(Framework, reason) {
  if (restartTimer) clearTimeout(restartTimer);
  restartTimer = setTimeout(async () => {
    if (restartInFlight) return;
    restartInFlight = true;
    try {
      logger('mercuryGuard', `Restarting Webex Framework (${reason})`, 'warn');
      if (Framework && typeof Framework.restart === 'function') {
        await Framework.restart();
        logger('mercuryGuard', 'Framework restart completed');
      } else {
        logger('mercuryGuard', 'Framework.restart() unavailable — skipping', 'warn');
      }
    } catch (err) {
      logger('mercuryGuard', `Framework restart failed: ${err.message}`, 'error');
    } finally {
      restartInFlight = false;
    }
  }, 2000);
}

function attachMercuryListeners(Framework) {
  try {
    const mercury = Framework?.webex?.internal?.mercury;
    if (!mercury?.on) {
      logger('mercuryGuard', 'Mercury plugin not available for event hooks', 'warn');
      return;
    }

    mercury.on('offline', (event) => {
      logger('mercuryGuard', `Mercury offline (${event?.type || 'unknown'})`, 'warn');
      scheduleFrameworkRestart(Framework, `mercury offline (${event?.type || 'unknown'})`);
    });

    mercury.on('online', () => {
      logger('mercuryGuard', 'Mercury online');
    });
  } catch (err) {
    logger('mercuryGuard', `Could not attach mercury listeners: ${err.message}`, 'warn');
  }
}

function startHealthPoll(Framework) {
  const intervalMs = Number(process.env.MERCURY_HEALTH_POLL_MS) || 5 * 60 * 1000;
  setInterval(() => {
    const state = getMercuryState(Framework);
    if (!state) return;
    // readyState: 0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
    if (!state.connected && !state.connecting && state.readyState !== 1 && state.readyState !== 0) {
      logger(
        'mercuryGuard',
        `Mercury appears disconnected (connected=${state.connected}, readyState=${state.readyState})`,
        'warn'
      );
      scheduleFrameworkRestart(Framework, `health poll (readyState=${state.readyState})`);
    }
  }, intervalMs).unref?.();
}

/**
 * Install process-level guards and mercury lifecycle hooks.
 * Safe to call once at boot; subsequent calls are ignored.
 *
 * @param {object} Framework - webex-node-bot-framework instance
 */
export function installMercuryGuard(Framework) {
  if (guardInstalled) return;
  guardInstalled = true;

  process.on('unhandledRejection', (reason) => {
    if (!isMercuryInvalidStateError(reason)) return;
    const state = getMercuryState(Framework);
    logger(
      'mercuryGuard',
      `Intercepted unhandled Mercury INVALID_STATE_ERROR (readyState=${state?.readyState ?? '?'}) — process kept alive`,
      'warn'
    );
    scheduleFrameworkRestart(Framework, 'INVALID_STATE_ERROR (unhandledRejection)');
  });

  process.on('uncaughtException', (err) => {
    if (isMercuryInvalidStateError(err)) {
      const state = getMercuryState(Framework);
      logger(
        'mercuryGuard',
        `Intercepted uncaught Mercury INVALID_STATE_ERROR (readyState=${state?.readyState ?? '?'}) — process kept alive`,
        'warn'
      );
      scheduleFrameworkRestart(Framework, 'INVALID_STATE_ERROR (uncaughtException)');
      return;
    }
    logger('process', `Fatal uncaught exception: ${err.message}`, 'error');
    process.exit(1);
  });

  Framework.on('initialized', () => {
    attachMercuryListeners(Framework);
    startHealthPoll(Framework);
    logger('mercuryGuard', 'Mercury crash guard installed');
  });

  // Optional dev/test hook: set SIMULATE_MERCURY_CRASH=1 to verify the guard
  // without waiting for a real websocket blip.
  if (process.env.SIMULATE_MERCURY_CRASH === '1') {
    setTimeout(() => {
      const fakeErr = new Error('INVALID_STATE_ERROR');
      fakeErr.stack =
        'Error: INVALID_STATE_ERROR\n' +
        '    at /app/node_modules/@webex/internal-plugin-mercury/dist/socket/socket-base.js:321:25\n' +
        '    at Socket.send (/app/node_modules/@webex/internal-plugin-mercury/dist/socket/socket-base.js:319:14)\n' +
        '    at Socket._acknowledge (/app/node_modules/@webex/internal-plugin-mercury/dist/socket/socket-base.js:346:19)';
      Promise.reject(fakeErr);
    }, 5000).unref?.();
  }
}

export default { installMercuryGuard, isMercuryInvalidStateError, getMercuryState };
