const dgram = require('node:dgram');
const http = require('node:http');
const https = require('node:https');
const { URL } = require('node:url');

const DEFAULT_INTERVAL_MS = 5 * 60 * 1000;
const DEFAULT_DRIFT_THRESHOLD_MS = 300_000;
const DEFAULT_HEARTBEAT_PATH = '/api/v1/health/time';
const DEFAULT_NTP_SERVER = 'pool.ntp.org';
const DEFAULT_NTP_PORT = 123;
const NTP_TIMEOUT_MS = 3000;
const HTTP_TIMEOUT_MS = 8000;

let intervalHandle = null;
let activeSocket = null;
let activeTimer = null;
const state = {
  lastSync: null,
  remoteOffsetMs: null,
  ntpOffsetMs: null,
  driftMs: null,
  driftThresholdMs: DEFAULT_DRIFT_THRESHOLD_MS,
  isDriftSuspected: false,
  running: false,
};

function parseIntEnv(name, fallback) {
  const raw = process.env[name];
  if (raw === undefined || raw === null || raw === '') return fallback;
  const parsed = parseInt(raw, 10);
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
}

function readNtp(server, port) {
  return new Promise((resolve) => {
    const socket = dgram.createSocket('udp4');
    activeSocket = socket;
    const packet = Buffer.alloc(48);
    packet[0] = 0x1b;
    const finished = (value) => {
      try { socket.close(); } catch (_) {}
      if (activeSocket === socket) activeSocket = null;
      resolve(value);
    };
    const fallback = setTimeout(() => finished(null), NTP_TIMEOUT_MS);
    if (fallback.unref) fallback.unref();
    socket.on('error', () => { clearTimeout(fallback); finished(null); });
    socket.on('message', (msg) => {
      clearTimeout(fallback);
      if (msg.length < 48) return finished(null);
      const secs = msg.readUInt32BE(40);
      const frac = msg.readUInt32BE(44);
      const ntpMs = (secs - 2208988800) * 1000 + Math.round((frac * 1000) / 0x100000000);
      finished(ntpMs - Date.now());
    });
    socket.send(packet, 0, 48, port, server, (err) => {
      if (err) { clearTimeout(fallback); finished(null); }
    });
  });
}

function readRemote(superAdminUrl, heartbeatPath) {
  return new Promise((resolve) => {
    let url;
    try { url = new URL(superAdminUrl + heartbeatPath); }
    catch (_) { return resolve(null); }
    const lib = url.protocol === 'https:' ? https : http;
    const req = lib.request({
      method: 'GET',
      hostname: url.hostname,
      port: url.port || (url.protocol === 'https:' ? 443 : 80),
      path: url.pathname + url.search,
      timeout: HTTP_TIMEOUT_MS,
      headers: { Accept: 'application/json' },
    }, (res) => {
      const chunks = [];
      res.on('data', (c) => chunks.push(c));
      res.on('end', () => {
        if (res.statusCode < 200 || res.statusCode >= 300) return resolve(null);
        try {
          const body = Buffer.concat(chunks).toString('utf8');
          const parsed = JSON.parse(body);
          const stamp = parsed.timestamp || parsed.now || parsed.time || parsed.ts;
          if (!stamp) return resolve(null);
          const remoteMs = new Date(stamp).getTime();
          if (!Number.isFinite(remoteMs)) return resolve(null);
          resolve(remoteMs - Date.now());
        } catch (_) {
          resolve(null);
        }
      });
    });
    req.on('error', () => resolve(null));
    req.on('timeout', () => { req.destroy(); resolve(null); });
    req.end();
  });
}

function closeOpenSocket() {
  if (activeSocket) {
    try { activeSocket.close(); } catch (_) {}
    activeSocket = null;
  }
  if (activeTimer) {
    clearTimeout(activeTimer);
    activeTimer = null;
  }
}

async function tick(opts) {
  const { superAdminUrl, heartbeatPath, driftMsThreshold, ntpServer, ntpPort } = opts;
  const before = { ...state };
  try {
    const tasks = [];
    if (superAdminUrl) tasks.push(readRemote(superAdminUrl, heartbeatPath));
    if (ntpServer) tasks.push(readNtp(ntpServer, ntpPort));
    const [remoteOff, ntpOff] = await Promise.all(tasks);
    if (typeof remoteOff === 'number') {
      state.remoteOffsetMs = remoteOff;
      state.lastSync = Date.now();
    }
    if (typeof ntpOff === 'number') {
      state.ntpOffsetMs = ntpOff;
      if (!state.lastSync) state.lastSync = Date.now();
    }
    const samples = [state.remoteOffsetMs, state.ntpOffsetMs].filter((v) => typeof v === 'number');
    if (samples.length > 0) {
      state.driftMs = Math.max(...samples.map((v) => Math.abs(v)));
      state.isDriftSuspected = state.driftMs > driftMsThreshold;
    } else {
      state.driftMs = before.driftMs;
      state.isDriftSuspected = before.isDriftSuspected;
    }
    if (typeof opts.onUpdate === 'function') {
      try { opts.onUpdate(getState()); } catch (e) {
        console.warn('[license-time-sync] onUpdate callback threw:', e.message);
      }
    }
  } catch (e) {
    console.warn('[license-time-sync] heartbeat tick failed:', e.message);
  } finally {
    closeOpenSocket();
  }
}

function start(options = {}) {
  if (intervalHandle) return;
  const intervalMs = options.intervalMs != null
    ? options.intervalMs
    : parseIntEnv('LICENSE_HEARTBEAT_INTERVAL_MS', DEFAULT_INTERVAL_MS);
  const driftMsThreshold = options.driftMsThreshold != null
    ? options.driftMsThreshold
    : parseIntEnv('LICENSE_CLOCK_DRIFT_MS', DEFAULT_DRIFT_THRESHOLD_MS);
  const superAdminUrl = options.superAdminUrl != null
    ? options.superAdminUrl
    : (process.env.SUPERADMIN_HEARTBEAT_URL || '');
  const heartbeatPath = options.heartbeatPath || DEFAULT_HEARTBEAT_PATH;
  const ntpServer = options.ntpServer != null
    ? options.ntpServer
    : (process.env.NTP_SERVER || DEFAULT_NTP_SERVER);
  const ntpPort = options.ntpPort != null ? options.ntpPort : DEFAULT_NTP_PORT;

  state.driftThresholdMs = driftMsThreshold;
  state.running = !!superAdminUrl;

  if (!superAdminUrl) {
    console.log('[license-time-sync] idle (no SUPERADMIN_HEARTBEAT_URL configured)');
    return;
  }

  const tickOpts = { superAdminUrl, heartbeatPath, driftMsThreshold, ntpServer, ntpPort, onUpdate: options.onUpdate };
  intervalHandle = setInterval(() => { tick(tickOpts); }, intervalMs);
  if (intervalHandle.unref) intervalHandle.unref();
  console.log(`[license-time-sync] started (interval ${intervalMs}ms, driftThreshold ${driftMsThreshold}ms)`);
  tick(tickOpts);
}

function stop() {
  if (intervalHandle) {
    clearInterval(intervalHandle);
    intervalHandle = null;
  }
  closeOpenSocket();
  state.running = false;
  console.log('[license-time-sync] stopped');
}

function getState() {
  return {
    lastSync: state.lastSync,
    remoteOffsetMs: state.remoteOffsetMs,
    ntpOffsetMs: state.ntpOffsetMs,
    driftMs: state.driftMs,
    driftThresholdMs: state.driftThresholdMs,
    isDriftSuspected: state.isDriftSuspected,
  };
}

module.exports = { start, stop, getState };