'use strict';

const DEFAULT_HTTP_FORCE_AFTER_MS = 1000;
const DEFAULT_HTTP_TIMEOUT_MS = 2000;
const DEFAULT_WS_GRACE_MS = 750;

function _unref(timer) {
  if (timer && typeof timer.unref === 'function') timer.unref();
  return timer;
}

function installHttpConnectionTracking(listenerServers = []) {
  for (const listener of listenerServers) {
    const server = listener && listener.server;
    if (!server || server.__ctmShutdownTrackingInstalled) continue;
    const sockets = new Set();
    server.on('connection', (socket) => {
      sockets.add(socket);
      socket.on('close', () => sockets.delete(socket));
    });
    listener.trackedSockets = sockets;
    Object.defineProperty(server, '__ctmShutdownTrackingInstalled', { value: true });
  }
}

function stopAcceptingHttpRequests(listenerServers = [], options = {}) {
  const forceAfterMs = Number.isFinite(Number(options.forceAfterMs))
    ? Math.max(0, Number(options.forceAfterMs))
    : DEFAULT_HTTP_FORCE_AFTER_MS;
  const timeoutMs = Number.isFinite(Number(options.timeoutMs))
    ? Math.max(forceAfterMs, Number(options.timeoutMs))
    : Math.max(DEFAULT_HTTP_TIMEOUT_MS, forceAfterMs);
  const logger = options.logger || console;

  return Promise.all(listenerServers.map((listener) => new Promise((resolve) => {
    const server = listener && listener.server;
    if (!server) return resolve({ id: listener?.id, closed: true, missing: true });

    let settled = false;
    const done = (result = {}) => {
      if (settled) return;
      settled = true;
      listener.started = false;
      if (forceTimer) clearTimeout(forceTimer);
      if (timeoutTimer) clearTimeout(timeoutTimer);
      resolve({ id: listener.id, ...result });
    };

    let forceTimer = null;
    let timeoutTimer = null;

    try {
      server.close((err) => {
        if (err && err.code !== 'ERR_SERVER_NOT_RUNNING') {
          try { logger.warn(`[shutdown] ${listener.id || 'http'} listener close error: ${err.message}`); } catch {}
        }
        done({ closed: true, error: err?.code || null });
      });
    } catch (err) {
      if (err?.code === 'ERR_SERVER_NOT_RUNNING') return done({ closed: true, alreadyClosed: true });
      try { logger.warn(`[shutdown] ${listener.id || 'http'} listener close threw: ${err.message}`); } catch {}
    }

    try { if (typeof server.closeIdleConnections === 'function') server.closeIdleConnections(); } catch {}

    forceTimer = _unref(setTimeout(() => {
      try { if (typeof server.closeAllConnections === 'function') server.closeAllConnections(); } catch {}
      const tracked = listener.trackedSockets || new Set();
      for (const socket of tracked) {
        try { if (socket && !socket.destroyed) socket.destroy(); } catch {}
      }
    }, forceAfterMs));

    timeoutTimer = _unref(setTimeout(() => {
      done({
        closed: false,
        timedOut: true,
        trackedSockets: listener.trackedSockets ? listener.trackedSockets.size : 0,
      });
    }, timeoutMs));
  })));
}

function closeWebSocketClients(wss, options = {}) {
  const clients = Array.from(wss?.clients || []);
  const code = Number.isFinite(Number(options.code)) ? Number(options.code) : 1012;
  const reason = String(options.reason || 'Service restart').slice(0, 123);
  const graceMs = Number.isFinite(Number(options.graceMs))
    ? Math.max(0, Number(options.graceMs))
    : DEFAULT_WS_GRACE_MS;
  const preCloseDelayMs = Number.isFinite(Number(options.preCloseDelayMs))
    ? Math.max(0, Number(options.preCloseDelayMs))
    : 0;

  return new Promise((resolve) => {
    if (!wss) return resolve({ closed: 0, terminated: 0, total: 0 });

    const startClose = () => {
      if (clients.length === 0) {
        try { wss.close(() => {}); } catch {}
        return resolve({ closed: 0, terminated: 0, total: 0 });
      }

      let closed = 0;
      let settled = false;
      const pending = new Set(clients);
      const listeners = new Map();
      let timer = null;

      const finish = (timedOut = false) => {
        if (settled) return;
        settled = true;
        if (timer) clearTimeout(timer);
        let terminated = 0;
        for (const ws of pending) {
          const onClose = listeners.get(ws);
          if (onClose) {
            try { ws.off('close', onClose); } catch {}
          }
          try {
            if (timedOut && ws.readyState !== ws.CLOSED) {
              ws.terminate();
              terminated += 1;
            }
          } catch {}
        }
        try { wss.close(() => {}); } catch {}
        resolve({ closed, terminated, total: clients.length, timedOut });
      };

      for (const ws of clients) {
        const onClose = () => {
          if (!pending.delete(ws)) return;
          closed += 1;
          if (pending.size === 0) finish(false);
        };
        listeners.set(ws, onClose);
        try { ws.once('close', onClose); } catch {}
        try {
          if (ws.readyState === ws.OPEN || ws.readyState === ws.CONNECTING) ws.close(code, reason);
          else if (ws.readyState === ws.CLOSED) onClose();
        } catch {
          onClose();
        }
      }

      if (!settled) timer = _unref(setTimeout(() => finish(true), graceMs));
    };

    if (preCloseDelayMs > 0) _unref(setTimeout(startClose, preCloseDelayMs));
    else startClose();
  });
}

module.exports = {
  closeWebSocketClients,
  installHttpConnectionTracking,
  stopAcceptingHttpRequests,
};
