#!/usr/bin/env node

const { execFile, spawn } = require("node:child_process");
const dns = require("node:dns");
const fs = require("node:fs");
const net = require("node:net");
const os = require("node:os");
const path = require("node:path");
const https = require("node:https");

const MAC_WG_INTERFACE = "nordvpnctl";
const STATE_DIR = path.join(os.homedir(), ".nordvpn-client");
const WG_STATE_DIR = path.join(STATE_DIR, "wireguard");
const WG_CONFIG_PATH = path.join(WG_STATE_DIR, `${MAC_WG_INTERFACE}.conf`);
const AUTH_CACHE_PATH = path.join(STATE_DIR, "auth.json");
const LAST_CONNECTION_PATH = path.join(STATE_DIR, "last-connection.json");
const MAC_DNS_STATE_PATH = path.join(STATE_DIR, "dns.json");
const MAC_TAILSCALE_STATE_PATH = path.join(STATE_DIR, "tailscale.json");
const MAC_TAILSCALE_SUPPRESS_PATH = path.join(STATE_DIR, "tailscale-suppressed");
const OPERATION_LOCK_PATH = path.join(STATE_DIR, "operation.lock");
const OPENCLAW_NORDVPN_CREDENTIALS_DIR = path.join(
  os.homedir(),
  ".openclaw",
  "workspace",
  ".clawdbot",
  "credentials",
  "nordvpn"
);
const DEFAULT_TOKEN_FILE = path.join(OPENCLAW_NORDVPN_CREDENTIALS_DIR, "token.txt");
const DEFAULT_PASSWORD_FILE = path.join(OPENCLAW_NORDVPN_CREDENTIALS_DIR, "password.txt");
const MAC_WG_HELPER_PATH = path.join(
  os.homedir(),
  ".openclaw",
  "workspace",
  "skills",
  "nordvpn-client",
  "scripts",
  "nordvpn-wireguard-helper.sh"
);
const CLIENT_IPV4 = "10.5.0.2";
const DNS_FALLBACK_RESOLVERS = ["1.1.1.1", "8.8.8.8"];
const NORDVPN_MAC_DNS_SERVERS = ["103.86.96.100", "103.86.99.100"];
const CONNECT_PERSISTENCE_ATTEMPTS = 6;
const CONNECT_PERSISTENCE_DELAY_MS = 2000;
const CONNECT_TOTAL_TIMEOUT_MS = 90000;
const POST_DNS_RESOLUTION_HOSTNAMES = ["www.google.com", "api.openai.com", "docs.openclaw.ai"];
const POST_DNS_RESOLUTION_TIMEOUT_MS = 4000;
const POST_DNS_SETTLE_DELAY_MS = 1500;
const REDACTED_PATH_KEYS = new Set(["cliPath", "appPath", "configPath", "helperPath", "tokenSource", "helperSecurity"]);

function sanitizeOutputPayload(payload) {
  if (Array.isArray(payload)) {
    return payload.map((value) => sanitizeOutputPayload(value));
  }
  if (!payload || typeof payload !== "object") {
    return payload;
  }

  const sanitized = {};
  for (const [key, value] of Object.entries(payload)) {
    if (REDACTED_PATH_KEYS.has(key)) {
      sanitized[key] = null;
      continue;
    }
    sanitized[key] = sanitizeOutputPayload(value);
  }
  return sanitized;
}

function printJson(payload, exitCode = 0, errorStream = false, debugOutput = false) {
  const body = `${JSON.stringify(debugOutput ? payload : sanitizeOutputPayload(payload), null, 2)}\n`;
  (errorStream ? process.stderr : process.stdout).write(body);
  process.exit(exitCode);
}

function usage() {
  return {
    usage: [
      "node scripts/nordvpn-client.js status",
      "node scripts/nordvpn-client.js install",
      "node scripts/nordvpn-client.js login",
      "node scripts/nordvpn-client.js verify",
      'node scripts/nordvpn-client.js verify --country "Italy"',
      'node scripts/nordvpn-client.js verify --country "Italy" --city "Milan"',
      'node scripts/nordvpn-client.js connect --country "Italy"',
      'node scripts/nordvpn-client.js connect --city "Milan"',
      'node scripts/nordvpn-client.js connect --country "Italy" --city "Milan"',
      "node scripts/nordvpn-client.js disconnect",
    ],
    env: [
      "NORDVPN_TOKEN",
      "NORDVPN_TOKEN_FILE",
      `default token file: ${DEFAULT_TOKEN_FILE}`,
      "NORDVPN_USERNAME",
      "NORDVPN_PASSWORD",
      "NORDVPN_PASSWORD_FILE",
      `default password file: ${DEFAULT_PASSWORD_FILE}`,
    ],
  };
}

function parseArgs(argv) {
  const args = { _: [] };
  for (let i = 0; i < argv.length; i += 1) {
    const token = argv[i];
    if (token.startsWith("--")) {
      const key = token.slice(2);
      const next = argv[i + 1];
      if (!next || next.startsWith("--")) {
        args[key] = true;
      } else {
        args[key] = next;
        i += 1;
      }
    } else {
      args._.push(token);
    }
  }
  return args;
}

function detectPlatform() {
  const raw = os.platform();
  if (raw === "darwin") return "darwin";
  if (raw === "linux") return "linux";
  return raw;
}

function readSecret(envName, fileEnvName) {
  if (process.env[envName]) return process.env[envName];

  const candidates = [];
  if (process.env[fileEnvName]) candidates.push(process.env[fileEnvName]);
  if (envName === "NORDVPN_TOKEN") candidates.push(DEFAULT_TOKEN_FILE);
  if (envName === "NORDVPN_PASSWORD") candidates.push(DEFAULT_PASSWORD_FILE);

  for (const candidate of candidates) {
    try {
      const value = fs.readFileSync(candidate, "utf8").trim();
      if (value) return value;
    } catch {
      // Continue.
    }
  }

  return "";
}

function normalizeLocation(value) {
  return `${value || ""}`.trim();
}

function normalizeForMatch(value) {
  return `${value || ""}`
    .normalize("NFKD")
    .replace(/[^\p{L}\p{N}\s-]/gu, " ")
    .replace(/\s+/g, " ")
    .trim()
    .toLowerCase();
}

function commandExists(name) {
  const pathEntries = `${process.env.PATH || ""}`.split(path.delimiter).filter(Boolean);
  for (const entry of pathEntries) {
    const full = path.join(entry, name);
    try {
      fs.accessSync(full, fs.constants.X_OK);
      return full;
    } catch {
      // Continue.
    }
  }

  const fallbackDirs = ["/usr/sbin", "/usr/bin", "/bin", "/usr/local/bin", "/opt/homebrew/bin"];
  for (const entry of fallbackDirs) {
    const full = path.join(entry, name);
    try {
      fs.accessSync(full, fs.constants.X_OK);
      return full;
    } catch {
      // Continue.
    }
  }

  return "";
}

function fileExists(target) {
  try {
    fs.accessSync(target);
    return true;
  } catch {
    return false;
  }
}

function ensureDir(target, mode = 0o700) {
  fs.mkdirSync(target, { recursive: true, mode });
  try {
    fs.chmodSync(target, mode);
  } catch {
    // Ignore chmod errors on existing directories.
  }
}

function readJsonFile(target) {
  try {
    return JSON.parse(fs.readFileSync(target, "utf8"));
  } catch {
    return null;
  }
}

function writeJsonFile(target, payload, mode = 0o600) {
  ensureDir(path.dirname(target));
  fs.writeFileSync(target, `${JSON.stringify(payload, null, 2)}\n`, { mode });
  try {
    fs.chmodSync(target, mode);
  } catch {
    // Ignore chmod errors.
  }
}

function writeTextFile(target, contents, mode = 0o600) {
  ensureDir(path.dirname(target));
  fs.writeFileSync(target, contents, { mode });
  try {
    fs.chmodSync(target, mode);
  } catch {
    // Ignore chmod errors.
  }
}

function runExec(command, args = [], options = {}) {
  return new Promise((resolve) => {
    execFile(command, args, { ...options, encoding: "utf8" }, (error, stdout, stderr) => {
      resolve({
        ok: !error,
        code: error && typeof error.code === "number" ? error.code : 0,
        stdout: stdout || "",
        stderr: stderr || "",
        error: error ? error.message : "",
      });
    });
  });
}

function runInteractive(command, args = [], options = {}) {
  return new Promise((resolve, reject) => {
    const child = spawn(command, args, { stdio: "inherit", ...options });
    child.on("error", reject);
    child.on("exit", (code) => resolve(code === 0));
  });
}

function runShell(command) {
  return runInteractive("bash", ["-lc", command]);
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function processExists(pid) {
  if (!Number.isInteger(pid) || pid <= 0) return false;
  try {
    process.kill(pid, 0);
    return true;
  } catch (error) {
    return error && error.code === "EPERM";
  }
}

function isOperationLockStale(lockRecord, options = {}) {
  if (!lockRecord || typeof lockRecord !== "object") return true;
  const now = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
  const maxAgeMs = Number.isFinite(options.maxAgeMs) ? options.maxAgeMs : CONNECT_TOTAL_TIMEOUT_MS;
  const startedAtMs = Number.isFinite(lockRecord.startedAtMs)
    ? lockRecord.startedAtMs
    : Date.parse(lockRecord.startedAt || "");

  if (!processExists(lockRecord.pid)) {
    return true;
  }
  if (!Number.isFinite(startedAtMs)) {
    return true;
  }
  return now - startedAtMs > maxAgeMs;
}

function cleanupOperationLock(lockPath = OPERATION_LOCK_PATH) {
  try {
    if (lockPath && fs.existsSync(lockPath)) {
      fs.unlinkSync(lockPath);
    }
  } catch {
    // Ignore cleanup errors.
  }
}

function acquireOperationLock(action, options = {}) {
  const lockPath = options.lockPath || OPERATION_LOCK_PATH;
  const nowMs = Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
  const maxAgeMs = Number.isFinite(options.maxAgeMs) ? options.maxAgeMs : CONNECT_TOTAL_TIMEOUT_MS;
  const existing = readJsonFile(lockPath);

  if (existing) {
    if (isOperationLockStale(existing, { nowMs, maxAgeMs })) {
      cleanupOperationLock(lockPath);
    } else {
      const activeAction = existing.action || "unknown";
      throw new Error(`Another nordvpn-client ${activeAction} operation is already running.`);
    }
  }

  const record = {
    action,
    pid: process.pid,
    startedAt: new Date(nowMs).toISOString(),
    startedAtMs: nowMs,
  };
  writeJsonFile(lockPath, record);

  return {
    lockPath,
    record,
    release() {
      const current = readJsonFile(lockPath);
      if (current && current.pid === record.pid && current.startedAtMs === record.startedAtMs) {
        cleanupOperationLock(lockPath);
      }
    },
  };
}

function detectMacWireguardActiveFromIfconfig(ifconfigOutput) {
  let inUtunBlock = false;
  const lines = `${ifconfigOutput || ""}`.split("\n");

  for (const line of lines) {
    if (/^utun\d+:/.test(line)) {
      inUtunBlock = true;
      continue;
    }
    if (/^\S/.test(line)) {
      inUtunBlock = false;
    }
    if (inUtunBlock && line.includes(`inet ${CLIENT_IPV4}`)) {
      return true;
    }
  }

  return false;
}

function buildMacTailscaleState(tailscaleWasActive) {
  return { tailscaleWasActive: Boolean(tailscaleWasActive) };
}

function markMacTailscaleRecoverySuppressed() {
  ensureDir(STATE_DIR);
  writeTextFile(MAC_TAILSCALE_SUPPRESS_PATH, `${new Date().toISOString()}\n`, 0o600);
}

function clearMacTailscaleRecoverySuppressed() {
  try {
    fs.unlinkSync(MAC_TAILSCALE_SUPPRESS_PATH);
  } catch {
    // Ignore cleanup errors.
  }
}

function inspectMacWireguardHelperSecurity(helperPath, deps = {}) {
  const fileExistsFn = deps.fileExists || fileExists;
  const statSyncFn = deps.statSync || fs.statSync;
  if (!helperPath || !fileExistsFn(helperPath)) {
    return {
      exists: false,
      hardened: false,
      ownerUid: null,
      groupGid: null,
      mode: null,
      reason: "Helper is missing.",
    };
  }

  let stat;
  try {
    stat = statSyncFn(helperPath);
  } catch (error) {
    return {
      exists: true,
      hardened: false,
      ownerUid: null,
      groupGid: null,
      mode: null,
      reason: error.message || "Unable to inspect helper security.",
    };
  }

  const mode = stat.mode & 0o777;
  if (stat.uid !== 0) {
    return {
      exists: true,
      hardened: false,
      ownerUid: stat.uid,
      groupGid: stat.gid,
      mode,
      reason: "Helper must be root-owned before privileged actions are trusted.",
    };
  }
  if ((mode & 0o022) !== 0) {
    return {
      exists: true,
      hardened: false,
      ownerUid: stat.uid,
      groupGid: stat.gid,
      mode,
      reason: "Helper must not be group- or world-writable.",
    };
  }

  return {
    exists: true,
    hardened: true,
    ownerUid: stat.uid,
    groupGid: stat.gid,
    mode,
    reason: "",
  };
}

function trimDiagnosticOutput(value, maxChars = 4000) {
  const text = `${value || ""}`.trim();
  if (!text) return "";
  if (text.length <= maxChars) return text;
  return `${text.slice(0, maxChars)}\n...[truncated]`;
}

function summarizeMacWireguardDiagnostics(diagnostics) {
  if (!diagnostics) return null;
  return {
    interfaceName: diagnostics.interfaceName || MAC_WG_INTERFACE,
    wgShowCaptured: Boolean(diagnostics.wgShow),
    ifconfigCaptured: Boolean(diagnostics.ifconfig),
    routesCaptured: Boolean(diagnostics.routes),
    processesCaptured: Boolean(diagnostics.processes),
    helperSecurity:
      diagnostics.helperSecurity && typeof diagnostics.helperSecurity === "object"
        ? {
            hardened: Boolean(diagnostics.helperSecurity.hardened),
            reason: diagnostics.helperSecurity.reason || "",
          }
        : null,
  };
}

async function collectMacWireguardDiagnostics(options = {}) {
  const runExecFn = options.runExec || runExec;
  const interfaceName = options.interfaceName || MAC_WG_INTERFACE;
  const wgPath = options.wgPath || commandExists("wg") || "/opt/homebrew/bin/wg";
  const helperSecurity =
    options.helperSecurity ||
    inspectMacWireguardHelperSecurity(options.helperPath || MAC_WG_HELPER_PATH, options.securityDeps || {});

  const [wgShow, ifconfig, routes, processes] = await Promise.all([
    runExecFn(wgPath, ["show", interfaceName]),
    runExecFn("ifconfig", [interfaceName]),
    runExecFn("route", ["-n", "get", "default"]),
    runExecFn("pgrep", ["-fl", "wireguard-go|wg-quick|nordvpnctl"]),
  ]);

  return {
    interfaceName,
    helperSecurity,
    wgShow: trimDiagnosticOutput(wgShow.stdout || wgShow.stderr || wgShow.error),
    ifconfig: trimDiagnosticOutput(ifconfig.stdout || ifconfig.stderr || ifconfig.error),
    routes: trimDiagnosticOutput(routes.stdout || routes.stderr || routes.error),
    processes: trimDiagnosticOutput(processes.stdout || processes.stderr || processes.error),
  };
}

function shouldResumeMacTailscale(state, currentlyActive) {
  return Boolean(state && state.tailscaleWasActive && !currentlyActive);
}

function buildMacDnsState(services) {
  return {
    services: (services || []).map((service) => ({
      name: service.name,
      dnsServers: Array.isArray(service.dnsServers) ? service.dnsServers : [],
      searchDomains: Array.isArray(service.searchDomains) ? service.searchDomains : [],
    })),
  };
}

function shouldManageMacDnsService(serviceName) {
  const normalized = `${serviceName || ""}`.trim().toLowerCase();
  if (!normalized) return false;
  return !["tailscale", "bridge", "thunderbolt bridge", "loopback", "vpn", "utun"].some((token) => normalized.includes(token));
}

function normalizeMacNetworksetupList(output) {
  return `${output || ""}`
    .split("\n")
    .map((line) => line.trim())
    .filter(Boolean)
    .filter((line) => !line.startsWith("An asterisk"))
    .map((line) => (line.startsWith("*") ? line.slice(1).trim() : line))
    .filter((line) => shouldManageMacDnsService(line));
}

function normalizeMacDnsCommandOutput(output) {
  const text = `${output || ""}`.trim();
  if (!text || text.includes("aren't any") || text.includes("There aren't any")) {
    return [];
  }
  return text
    .split("\n")
    .map((line) => line.trim())
    .filter(Boolean);
}

function isNordDnsServerList(dnsServers) {
  if (!Array.isArray(dnsServers) || !dnsServers.length) return false;
  const normalized = dnsServers.map((value) => `${value}`.trim()).filter(Boolean);
  if (!normalized.length) return false;
  return normalized.every((value) => NORDVPN_MAC_DNS_SERVERS.includes(value));
}

function buildAutomaticMacDnsState(serviceNames) {
  return {
    services: (serviceNames || []).map((name) => ({
      name,
      dnsServers: [],
      searchDomains: [],
    })),
  };
}

function shouldRejectMacDnsBaseline(state) {
  if (!state || !Array.isArray(state.services) || !state.services.length) return false;
  return state.services.every((service) => isNordDnsServerList(service.dnsServers || []));
}

async function listMacDnsServices() {
  const result = await runExec("networksetup", ["-listallnetworkservices"]);
  if (!result.ok) {
    throw new Error((result.stderr || result.stdout || result.error).trim() || "networksetup -listallnetworkservices failed");
  }
  return normalizeMacNetworksetupList(result.stdout);
}

async function readMacDnsStateForService(serviceName) {
  const dnsResult = await runExec("networksetup", ["-getdnsservers", serviceName]);
  if (!dnsResult.ok) {
    throw new Error((dnsResult.stderr || dnsResult.stdout || dnsResult.error).trim() || `networksetup -getdnsservers failed for ${serviceName}`);
  }
  const searchResult = await runExec("networksetup", ["-getsearchdomains", serviceName]);
  if (!searchResult.ok) {
    throw new Error((searchResult.stderr || searchResult.stdout || searchResult.error).trim() || `networksetup -getsearchdomains failed for ${serviceName}`);
  }
  return {
    name: serviceName,
    dnsServers: normalizeMacDnsCommandOutput(dnsResult.stdout),
    searchDomains: normalizeMacDnsCommandOutput(searchResult.stdout),
  };
}

async function snapshotMacDnsState() {
  const services = await listMacDnsServices();
  const snapshot = [];
  for (const serviceName of services) {
    snapshot.push(await readMacDnsStateForService(serviceName));
  }
  const state = buildMacDnsState(snapshot);
  writeJsonFile(MAC_DNS_STATE_PATH, state);
  return state;
}

async function setMacDnsServers(serviceName, dnsServers) {
  const args = ["-setdnsservers", serviceName];
  args.push(...(dnsServers && dnsServers.length ? dnsServers : ["Empty"]));
  const result = await runExec("networksetup", args);
  if (!result.ok) {
    throw new Error((result.stderr || result.stdout || result.error).trim() || `networksetup -setdnsservers failed for ${serviceName}`);
  }
}

async function setMacSearchDomains(serviceName, searchDomains) {
  const args = ["-setsearchdomains", serviceName];
  args.push(...(searchDomains && searchDomains.length ? searchDomains : ["Empty"]));
  const result = await runExec("networksetup", args);
  if (!result.ok) {
    throw new Error((result.stderr || result.stdout || result.error).trim() || `networksetup -setsearchdomains failed for ${serviceName}`);
  }
}

async function applyMacNordDns() {
  let snapshot = readJsonFile(MAC_DNS_STATE_PATH);
  if (!snapshot || !Array.isArray(snapshot.services) || !snapshot.services.length) {
    snapshot = await snapshotMacDnsState();
  } else if (shouldRejectMacDnsBaseline(snapshot)) {
    const services = await listMacDnsServices();
    snapshot = buildAutomaticMacDnsState(services);
    writeJsonFile(MAC_DNS_STATE_PATH, snapshot);
  }
  for (const service of snapshot.services || []) {
    await setMacDnsServers(service.name, NORDVPN_MAC_DNS_SERVERS);
    await setMacSearchDomains(service.name, []);
  }
  return snapshot;
}

async function restoreMacDnsIfNeeded() {
  const snapshot = readJsonFile(MAC_DNS_STATE_PATH);
  if (!snapshot || !Array.isArray(snapshot.services) || !snapshot.services.length) {
    return { restored: false };
  }
  const servicesToRestore = shouldRejectMacDnsBaseline(snapshot)
    ? buildAutomaticMacDnsState(snapshot.services.map((service) => service.name)).services
    : snapshot.services;
  for (const service of servicesToRestore) {
    await setMacDnsServers(service.name, service.dnsServers || []);
    await setMacSearchDomains(service.name, service.searchDomains || []);
  }
  try {
    fs.unlinkSync(MAC_DNS_STATE_PATH);
  } catch {
    // Ignore unlink errors.
  }
  return { restored: true };
}

function getMacTailscalePath(deps = {}) {
  const commandExistsFn = deps.commandExists || commandExists;
  const fileExistsFn = deps.fileExists || fileExists;
  const discovered = commandExistsFn("tailscale");
  if (discovered) return discovered;
  const fallbacks = ["/opt/homebrew/bin/tailscale", "/Applications/Tailscale.app/Contents/MacOS/Tailscale"];
  for (const fallback of fallbacks) {
    if (fileExistsFn(fallback)) return fallback;
  }
  return "tailscale";
}

function isMacTailscaleActive(status) {
  return Boolean(status && status.BackendState === "Running");
}

async function getMacTailscaleStatus() {
  const tailscale = getMacTailscalePath();
  const result = await runExec(tailscale, ["status", "--json"]);
  if (!result.ok) {
    throw new Error((result.stderr || result.stdout || result.error).trim() || "tailscale status --json failed");
  }
  const json = JSON.parse(result.stdout || "{}");
  return {
    active: isMacTailscaleActive(json),
    raw: json,
  };
}

async function stopMacTailscaleIfActive() {
  const status = await getMacTailscaleStatus();
  if (!status.active) {
    clearMacTailscaleRecoverySuppressed();
    writeJsonFile(MAC_TAILSCALE_STATE_PATH, buildMacTailscaleState(false));
    return { tailscaleWasActive: false };
  }
  markMacTailscaleRecoverySuppressed();
  const tailscale = getMacTailscalePath();
  const result = await runExec(tailscale, ["down"]);
  if (!result.ok) {
    clearMacTailscaleRecoverySuppressed();
    throw new Error((result.stderr || result.stdout || result.error).trim() || "tailscale down failed");
  }
  writeJsonFile(MAC_TAILSCALE_STATE_PATH, buildMacTailscaleState(true));
  return { tailscaleWasActive: true };
}

async function resumeMacTailscaleIfNeeded() {
  const state = readJsonFile(MAC_TAILSCALE_STATE_PATH);
  let currentStatus = null;
  try {
    currentStatus = await getMacTailscaleStatus();
  } catch {
    currentStatus = null;
  }
  if (!shouldResumeMacTailscale(state, currentStatus && currentStatus.active)) {
    clearMacTailscaleRecoverySuppressed();
    try {
      fs.unlinkSync(MAC_TAILSCALE_STATE_PATH);
    } catch {
      // Ignore unlink errors.
    }
    return { restored: false };
  }
  const tailscale = getMacTailscalePath();
  const result = await runExec(tailscale, ["up"]);
  if (!result.ok) {
    throw new Error((result.stderr || result.stdout || result.error).trim() || "tailscale up failed");
  }
  clearMacTailscaleRecoverySuppressed();
  try {
    fs.unlinkSync(MAC_TAILSCALE_STATE_PATH);
  } catch {
    // Ignore cleanup errors.
  }
  return { restored: true };
}

async function resolveHostnameWithFallback(hostname, options = {}) {
  const resolvers = options.resolvers || DNS_FALLBACK_RESOLVERS;
  const resolveWithResolver =
    options.resolveWithResolver ||
    (async (targetHostname, resolverIp) => {
      const resolver = new dns.promises.Resolver();
      resolver.setServers([resolverIp]);
      return resolver.resolve4(targetHostname);
    });

  for (const resolverIp of resolvers) {
    try {
      const addresses = await resolveWithResolver(hostname, resolverIp);
      if (Array.isArray(addresses) && addresses.length) {
        return addresses[0];
      }
    } catch {
      // Try the next resolver.
    }
  }

  return "";
}

async function verifySystemHostnameResolution(hostnames = POST_DNS_RESOLUTION_HOSTNAMES, options = {}) {
  const lookup =
    options.lookup ||
    ((hostname) =>
      dns.promises.lookup(hostname, {
        family: 4,
      }));
  const timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs > 0 ? options.timeoutMs : POST_DNS_RESOLUTION_TIMEOUT_MS;
  const settleDelayMs =
    Number.isFinite(options.settleDelayMs) && options.settleDelayMs >= 0 ? options.settleDelayMs : POST_DNS_SETTLE_DELAY_MS;
  const errors = [];

  if (settleDelayMs > 0) {
    await sleep(settleDelayMs);
  }

  for (const hostname of hostnames) {
    try {
      const result = await Promise.race([
        Promise.resolve().then(() => lookup(hostname)),
        sleep(timeoutMs).then(() => {
          throw new Error(`timeout after ${timeoutMs}ms`);
        }),
      ]);
      const address = Array.isArray(result) ? result[0] && result[0].address : result && result.address;
      if (address) {
        return {
          ok: true,
          hostname,
          address,
        };
      }
      errors.push(`${hostname}: no address returned`);
    } catch (error) {
      errors.push(`${hostname}: ${error.message || String(error)}`);
    }
  }

  return {
    ok: false,
    hostname: "",
    address: "",
    error: errors.join("; "),
  };
}

function buildLookupResult(address, options = {}) {
  if (options && options.all) {
    return [{ address, family: 4 }];
  }
  return [address, 4];
}

function cleanupMacWireguardState(paths = {}) {
  const targets = [paths.configPath || WG_CONFIG_PATH, paths.lastConnectionPath || LAST_CONNECTION_PATH];
  let cleaned = false;
  for (const target of targets) {
    try {
      if (target && fs.existsSync(target)) {
        fs.unlinkSync(target);
        cleaned = true;
      }
    } catch {
      // Ignore cleanup errors; caller will rely on current runtime state.
    }
  }
  return { cleaned };
}

function cleanupMacWireguardAndDnsState(paths = {}) {
  const cleaned = cleanupMacWireguardState(paths).cleaned;
  const dnsStatePath = paths.dnsStatePath || MAC_DNS_STATE_PATH;
  let dnsCleaned = false;
  try {
    if (dnsStatePath && fs.existsSync(dnsStatePath)) {
      fs.unlinkSync(dnsStatePath);
      dnsCleaned = true;
    }
  } catch {
    // Ignore cleanup errors; caller will rely on current runtime state.
  }
  return { cleaned: cleaned || dnsCleaned };
}

function shouldAttemptMacWireguardDisconnect(wireguardState) {
  if (!wireguardState) return false;
  if (wireguardState.active) return true;
  return Boolean(wireguardState.configPath || wireguardState.endpoint || wireguardState.lastConnection);
}

function isBenignMacWireguardAbsentError(message) {
  const normalized = `${message || ""}`.trim().toLowerCase();
  return (
    normalized.includes("is not a wireguard interface") ||
    normalized.includes("is not a known interface") ||
    normalized.includes("unable to access interface") ||
    normalized.includes("not found")
  );
}

function parseMacWireguardHelperStatus(output) {
  const parsed = {};
  for (const line of `${output || ""}`.split("\n")) {
    const trimmed = line.trim();
    if (!trimmed) continue;
    const separator = trimmed.indexOf("=");
    if (separator <= 0) continue;
    const key = trimmed.slice(0, separator).trim();
    const value = trimmed.slice(separator + 1).trim();
    parsed[key] = value;
  }

  return {
    active: ["1", "true", "yes", "on"].includes(`${parsed.active || ""}`.toLowerCase()),
    interfaceName: parsed.interfaceName || MAC_WG_INTERFACE,
    wireguardInterface: parsed.wireguardInterface || null,
    configPath: parsed.configPath || null,
    raw: `${output || ""}`.trim(),
  };
}

async function getMacWireguardHelperStatus(installProbe, options = {}) {
  const runSudoWireguardFn = options.runSudoWireguard || runSudoWireguard;
  const result = await runSudoWireguardFn(installProbe, "probe");
  const parsed = parseMacWireguardHelperStatus(result.stdout || result.stderr || "");
  return {
    ...parsed,
    ok: result.ok,
    error: result.ok ? "" : (result.stderr || result.stdout || result.error).trim(),
  };
}

async function checkMacWireguardPersistence(target, options = {}) {
  const attempts = Math.max(1, Number(options.attempts || CONNECT_PERSISTENCE_ATTEMPTS));
  const delayMs = Math.max(0, Number(options.delayMs || CONNECT_PERSISTENCE_DELAY_MS));
  const getHelperStatus = options.getHelperStatus || (async () => ({ active: false, interfaceName: MAC_WG_INTERFACE }));
  const verifyConnectionFn = options.verifyConnection || verifyConnection;
  const sleepFn = options.sleep || sleep;
  let lastHelperStatus = { active: false, interfaceName: MAC_WG_INTERFACE };
  let lastVerified = { ok: false, ipInfo: null };

  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    try {
      lastHelperStatus = await getHelperStatus();
    } catch (error) {
      lastHelperStatus = {
        active: false,
        interfaceName: MAC_WG_INTERFACE,
        error: error.message || String(error),
      };
    }

    try {
      lastVerified = await verifyConnectionFn(target);
    } catch (error) {
      lastVerified = {
        ok: false,
        ipInfo: {
          ok: false,
          error: error.message || String(error),
        },
      };
    }

    if (lastHelperStatus.active && lastVerified.ok) {
      return {
        stable: true,
        attempts: attempt,
        helperStatus: lastHelperStatus,
        verified: lastVerified,
      };
    }

    if (attempt < attempts) {
      await sleepFn(delayMs);
    }
  }

  return {
    stable: false,
    attempts,
    helperStatus: lastHelperStatus,
    verified: lastVerified,
  };
}

function shouldFinalizeMacWireguardConnect(connectResult, verified, persistence) {
  return Boolean(
    connectResult &&
      connectResult.backend === "wireguard" &&
      verified &&
      verified.ok &&
      persistence &&
      persistence.stable
  );
}

function normalizeSuccessfulConnectState(state, connectResult, verified) {
  if (
    !state ||
    state.platform !== "darwin" ||
    state.controlMode !== "wireguard" ||
    !connectResult ||
    connectResult.backend !== "wireguard" ||
    !verified ||
    !verified.ok
  ) {
    return state;
  }

  return {
    ...state,
    connected: true,
    wireguard: state.wireguard
      ? {
          ...state.wireguard,
          active: true,
          endpoint: connectResult.server && connectResult.server.hostname ? `${connectResult.server.hostname}:51820` : state.wireguard.endpoint,
        }
      : state.wireguard,
  };
}

function normalizeStatusState(state) {
  if (
    !state ||
    state.platform !== "darwin" ||
    state.controlMode !== "wireguard" ||
    state.connected ||
    !state.wireguard ||
    state.wireguard.active ||
    !state.wireguard.endpoint ||
    !state.wireguard.lastConnection ||
    !state.publicIp ||
    !state.publicIp.ok
  ) {
    return state;
  }

  const target =
    state.wireguard.lastConnection.resolvedTarget ||
    state.wireguard.lastConnection.requestedTarget;
  if (!target || !locationMatches(state.publicIp, target)) {
    return state;
  }

  return {
    ...state,
    connected: true,
    wireguard: {
      ...state.wireguard,
      active: true,
    },
  };
}

function fetchJson(url, headers = {}) {
  return new Promise((resolve) => {
    const targetUrl = new URL(url);
    const req = https.get(
      targetUrl,
      {
        headers: {
          "User-Agent": "nordvpn-client-skill/1.0",
          Accept: "application/json",
          ...headers,
        },
        lookup: (hostname, options, callback) => {
          const ipVersion = net.isIP(hostname);
          if (ipVersion) {
            callback(null, hostname, ipVersion);
            return;
          }
          resolveHostnameWithFallback(hostname)
            .then((address) => {
              if (address) {
                const result = buildLookupResult(address, options);
                if (options && options.all) {
                  callback(null, result);
                } else {
                  callback(null, result[0], result[1]);
                }
                return;
              }
              dns.lookup(hostname, options, callback);
            })
            .catch(() => dns.lookup(hostname, options, callback));
        },
      },
      (res) => {
        let data = "";
        res.on("data", (chunk) => {
          data += chunk;
        });
        res.on("end", () => {
          try {
            resolve({ ok: true, statusCode: res.statusCode || 0, json: JSON.parse(data) });
          } catch (error) {
            resolve({
              ok: false,
              statusCode: res.statusCode || 0,
              error: error.message,
              raw: data,
            });
          }
        });
      }
    );
    req.on("error", (error) => resolve({ ok: false, error: error.message }));
    req.setTimeout(15000, () => {
      req.destroy(new Error("timeout"));
    });
  });
}

async function getPublicIpInfo() {
  const lookup = await fetchJson("https://ipapi.co/json/");
  if (!lookup.ok) return { ok: false, error: lookup.error || "lookup failed" };

  const json = lookup.json || {};
  return {
    ok: true,
    ip: json.ip || "",
    city: json.city || "",
    region: json.region || "",
    country: json.country_name || json.country || "",
    countryCode: json.country_code || "",
    org: json.org || "",
    latitude: Number.isFinite(Number(json.latitude)) ? Number(json.latitude) : null,
    longitude: Number.isFinite(Number(json.longitude)) ? Number(json.longitude) : null,
  };
}

async function probeCliStatus(cliPath) {
  const status = await runExec(cliPath, ["status"]);
  const account = await runExec(cliPath, ["account"]);
  const countries = await runExec(cliPath, ["countries"]);
  return { status, account, countries };
}

async function probeMacWireguard() {
  const wgPath = commandExists("wg");
  const wgQuickPath = commandExists("wg-quick");
  const wireguardGoPath = commandExists("wireguard-go");
  const helperPath = fileExists(MAC_WG_HELPER_PATH) ? MAC_WG_HELPER_PATH : null;
  const helperSecurity = inspectMacWireguardHelperSecurity(helperPath);
  const sudoProbe = helperPath ? await runExec("sudo", ["-n", helperPath, "probe"]) : { ok: false };
  const helperStatus = helperPath && sudoProbe.ok ? parseMacWireguardHelperStatus(sudoProbe.stdout || sudoProbe.stderr || "") : null;
  let active = false;
  let showRaw = "";
  let endpoint = "";
  let ifconfigRaw = "";

  if (helperStatus) {
    active = helperStatus.active;
  }

  if (wgPath) {
    const show = await runExec(wgPath, ["show", MAC_WG_INTERFACE]);
    active = active || show.ok;
    showRaw = (show.stdout || show.stderr).trim();
    if (show.ok) {
      const endpointResult = await runExec(wgPath, ["show", MAC_WG_INTERFACE, "endpoints"]);
      endpoint = (endpointResult.stdout || "").trim();
    }
  }

  const ifconfig = await runExec("ifconfig");
  ifconfigRaw = (ifconfig.stdout || ifconfig.stderr).trim();
  if (!active) {
    active = detectMacWireguardActiveFromIfconfig(ifconfigRaw);
  }
  if (!endpoint && fileExists(WG_CONFIG_PATH)) {
    const configText = fs.readFileSync(WG_CONFIG_PATH, "utf8");
    const match = configText.match(/^Endpoint\s*=\s*(.+)$/m);
    if (match) endpoint = match[1].trim();
  }

  return {
    wgPath: wgPath || null,
    wgQuickPath: wgQuickPath || null,
    wireguardGoPath: wireguardGoPath || null,
    helperPath,
    helperSecurity,
    dependenciesReady: Boolean(wgPath && wgQuickPath && wireguardGoPath),
    sudoReady: sudoProbe.ok,
    interfaceName: helperStatus && helperStatus.interfaceName ? helperStatus.interfaceName : MAC_WG_INTERFACE,
    configPath: (helperStatus && helperStatus.configPath) || (fileExists(WG_CONFIG_PATH) ? WG_CONFIG_PATH : null),
    active,
    endpoint: endpoint || null,
    showRaw,
    ifconfigRaw,
    authCache: readJsonFile(AUTH_CACHE_PATH),
    lastConnection: readJsonFile(LAST_CONNECTION_PATH),
  };
}

async function probeInstallation(platform) {
  const cliPath = commandExists("nordvpn");
  const appPath = platform === "darwin" ? "/Applications/NordVPN.app" : "";
  const brewPath = platform === "darwin" ? commandExists("brew") : "";
  const tokenAvailable = Boolean(readSecret("NORDVPN_TOKEN", "NORDVPN_TOKEN_FILE"));

  let cliProbe = null;
  if (cliPath) {
    cliProbe = await probeCliStatus(cliPath);
  }

  let wireguard = null;
  if (platform === "darwin") {
    wireguard = await probeMacWireguard();
  }

  return {
    platform,
    cliPath,
    appPath,
    appInstalled: Boolean(appPath && fileExists(appPath)),
    brewPath,
    tokenAvailable,
    installed:
      platform === "darwin"
        ? Boolean(cliPath) || Boolean(appPath && fileExists(appPath)) || Boolean(wireguard && wireguard.dependenciesReady)
        : Boolean(cliPath),
    cliProbe,
    wireguard,
  };
}

function inferAuthState(probe, installProbe) {
  if (probe && probe.account) {
    const blob = `${probe.account.stdout}\n${probe.account.stderr}`.toLowerCase();
    if (!blob.trim()) return null;
    if (probe.account.ok && !blob.includes("not logged")) return true;
    if (blob.includes("not logged") || blob.includes("login")) return false;
  }

  if (installProbe.platform === "darwin" && installProbe.wireguard) {
    if (installProbe.wireguard.authCache) return true;
    if (installProbe.tokenAvailable) return null;
  }

  return null;
}

function inferConnectionState(probe, installProbe) {
  if (probe && probe.status) {
    const blob = `${probe.status.stdout}\n${probe.status.stderr}`.toLowerCase();
    if (!blob.trim()) return null;
    if (blob.includes("connected")) return true;
    if (blob.includes("disconnected") || blob.includes("not connected")) return false;
  }

  if (installProbe.platform === "darwin" && installProbe.wireguard) {
    return installProbe.wireguard.active;
  }

  return null;
}

function buildStateSummary(installProbe, ipInfo) {
  const cliProbe = installProbe.cliProbe;
  let controlMode = "unavailable";
  let automaticControl = false;
  let loginMode = "unsupported";
  let connectMode = "unsupported";
  let recommendedAction = "Install NordVPN first.";

  if (installProbe.cliPath) {
    controlMode = "cli";
    automaticControl = true;
    loginMode = "cli";
    connectMode = "cli";
    recommendedAction = "Use login/connect/disconnect through the nordvpn CLI.";
  } else if (installProbe.platform === "darwin" && installProbe.wireguard && installProbe.wireguard.dependenciesReady) {
    controlMode = "wireguard";
    automaticControl = true;
    loginMode = "wireguard-token";
    connectMode = "wireguard";
    recommendedAction = installProbe.tokenAvailable
      ? installProbe.wireguard.sudoReady
        ? "Use token-based WireGuard automation on macOS."
        : `WireGuard tooling and token are available, but connect/disconnect require non-interactive sudo for ${MAC_WG_HELPER_PATH}. Allow that helper in sudoers, then rerun login/connect.`
      : `Set NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or place the token at ${DEFAULT_TOKEN_FILE} for automated macOS NordLynx/WireGuard connects.`;
  } else if (installProbe.platform === "darwin" && installProbe.appInstalled) {
    controlMode = "app-manual";
    automaticControl = false;
    loginMode = "app-manual";
    connectMode = "app-manual";
    recommendedAction = installProbe.tokenAvailable
      ? "NordVPN.app is installed, but automated macOS connects also require wireguard-go and wireguard-tools. Run 'node scripts/nordvpn-client.js install' to install them with Homebrew."
      : `NordVPN.app is installed. For automated macOS connects, run 'node scripts/nordvpn-client.js install' to install wireguard-go and wireguard-tools with Homebrew, then set NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or place the token at ${DEFAULT_TOKEN_FILE} and run login.`;
  } else if (installProbe.platform === "darwin" && installProbe.brewPath) {
    controlMode = "wireguard-bootstrap";
    automaticControl = false;
    loginMode = "wireguard-token";
    connectMode = "wireguard";
    recommendedAction = "Run 'node scripts/nordvpn-client.js install' to install wireguard-go and wireguard-tools with Homebrew for automated macOS NordLynx connects.";
  }

  return {
    platform: installProbe.platform,
    installed: installProbe.installed,
    cliAvailable: Boolean(installProbe.cliPath),
    cliPath: installProbe.cliPath || null,
    appInstalled: installProbe.appInstalled,
    appPath: installProbe.appInstalled ? installProbe.appPath : null,
    brewAvailable: Boolean(installProbe.brewPath),
    tokenAvailable: installProbe.tokenAvailable,
    controlMode,
    automaticControl,
    loginMode,
    connectMode,
    recommendedAction,
    authenticated: inferAuthState(cliProbe, installProbe),
    connected: inferConnectionState(cliProbe, installProbe),
    localStatusRaw: cliProbe && cliProbe.status ? (cliProbe.status.stdout || cliProbe.status.stderr).trim() : "",
    wireguard: installProbe.wireguard
      ? {
          dependenciesReady: installProbe.wireguard.dependenciesReady,
          sudoReady: installProbe.wireguard.sudoReady,
          interfaceName: installProbe.wireguard.interfaceName,
          active: installProbe.wireguard.active,
          configPath: installProbe.wireguard.configPath,
          endpoint: installProbe.wireguard.endpoint,
          wgPath: installProbe.wireguard.wgPath,
          wgQuickPath: installProbe.wireguard.wgQuickPath,
          wireguardGoPath: installProbe.wireguard.wireguardGoPath,
          helperPath: installProbe.wireguard.helperPath,
          helperSecurity: installProbe.wireguard.helperSecurity,
          authCache: installProbe.wireguard.authCache,
          lastConnection: installProbe.wireguard.lastConnection,
        }
      : null,
    publicIp: ipInfo && ipInfo.ok ? ipInfo : null,
  };
}

async function installNordvpn(installProbe) {
  if (installProbe.platform === "darwin") {
    if (!installProbe.brewPath) {
      throw new Error("Homebrew is required on macOS to bootstrap wireguard-go and wireguard-tools.");
    }

    const toInstall = [];
    if (!installProbe.wireguard || !installProbe.wireguard.wireguardGoPath) toInstall.push("wireguard-go");
    if (!installProbe.wireguard || !installProbe.wireguard.wgPath || !installProbe.wireguard.wgQuickPath) {
      toInstall.push("wireguard-tools");
    }

    if (!toInstall.length) {
      return { changed: false, message: "macOS WireGuard tooling is already installed." };
    }

    const ok = await runInteractive(installProbe.brewPath, ["install", ...toInstall]);
    if (!ok) {
      throw new Error(`brew install ${toInstall.join(" ")} failed`);
    }
    return {
      changed: true,
      message: `Installed ${toInstall.join(", ")} for the macOS WireGuard/NordLynx backend.`,
    };
  }

  if (installProbe.installed) {
    return { changed: false, message: "NordVPN is already installed." };
  }

  if (installProbe.platform === "linux") {
    const command = [
      'TMPFILE="$(mktemp)"',
      'if command -v curl >/dev/null 2>&1; then curl -fsSL https://downloads.nordcdn.com/apps/linux/install.sh -o "${TMPFILE}"; ' +
        'elif command -v wget >/dev/null 2>&1; then wget -qO "${TMPFILE}" https://downloads.nordcdn.com/apps/linux/install.sh; ' +
        "else echo 'curl or wget required' >&2; exit 1; fi",
      'sh "${TMPFILE}"',
      'rm -f "${TMPFILE}"',
    ].join(" && ");
    const ok = await runShell(command);
    if (!ok) {
      throw new Error("NordVPN Linux installer failed");
    }
    return { changed: true, message: "Installed NordVPN via the official Linux installer." };
  }

  throw new Error(`Unsupported platform: ${installProbe.platform}`);
}

async function openMacApp() {
  const ok = await runInteractive("open", ["-a", "NordVPN"]);
  if (!ok) throw new Error("Failed to open NordVPN.app");
}

async function fetchNordCredentials(token) {
  const result = await fetchJson("https://api.nordvpn.com/v1/users/services/credentials", {
    Authorization: `Bearer token:${token}`,
  });
  if (!result.ok) {
    throw new Error(result.error || `NordVPN credentials request failed (${result.statusCode || "unknown"})`);
  }
  if (result.statusCode >= 400) {
    const apiMessage = result.json && result.json.errors && result.json.errors.message;
    throw new Error(apiMessage || `NordVPN credentials request failed (${result.statusCode})`);
  }
  if (!result.json || !result.json.nordlynx_private_key) {
    throw new Error("NordVPN credentials response did not include nordlynx_private_key.");
  }
  return result.json;
}

async function fetchNordCountries() {
  const result = await fetchJson("https://api.nordvpn.com/v1/servers/countries");
  if (!result.ok || result.statusCode >= 400 || !Array.isArray(result.json)) {
    throw new Error(result.error || `NordVPN countries request failed (${result.statusCode || "unknown"})`);
  }
  return result.json;
}

function matchByName(candidates, nameAccessor, input, label) {
  const needle = normalizeForMatch(input);
  if (!needle) return [];
  const exact = candidates.filter((item) => normalizeForMatch(nameAccessor(item)) === needle);
  if (exact.length) return exact;
  const prefix = candidates.filter((item) => normalizeForMatch(nameAccessor(item)).startsWith(needle));
  if (prefix.length) return prefix;
  return candidates.filter((item) => normalizeForMatch(nameAccessor(item)).includes(needle));
}

function resolveTargetMetadata(countries, target) {
  let country = null;

  if (target.country) {
    const countryMatches = matchByName(countries, (item) => item.name, target.country, "country");
    if (!countryMatches.length) {
      throw new Error(`Could not find NordVPN country match for \"${target.country}\".`);
    }
    if (countryMatches.length > 1) {
      throw new Error(
        `Country \"${target.country}\" is ambiguous: ${countryMatches.slice(0, 5).map((item) => item.name).join(", ")}.`
      );
    }
    country = countryMatches[0];
  }

  let city = null;
  if (target.city) {
    if (country) {
      const cities = Array.isArray(country.cities) ? country.cities : [];
      const cityMatches = matchByName(cities, (item) => item.name, target.city, "city");
      if (!cityMatches.length) {
        throw new Error(`Could not find city \"${target.city}\" in ${country.name}.`);
      }
      if (cityMatches.length > 1) {
        throw new Error(
          `City \"${target.city}\" is ambiguous in ${country.name}: ${cityMatches.slice(0, 5).map((item) => item.name).join(", ")}.`
        );
      }
      city = cityMatches[0];
    } else {
      const globalMatches = [];
      for (const candidateCountry of countries) {
        for (const candidateCity of candidateCountry.cities || []) {
          if (matchByName([candidateCity], (item) => item.name, target.city, "city").length) {
            globalMatches.push({ country: candidateCountry, city: candidateCity });
          }
        }
      }
      if (!globalMatches.length) {
        throw new Error(`Could not find NordVPN city match for \"${target.city}\".`);
      }
      if (globalMatches.length > 1) {
        const suggestions = globalMatches
          .slice(0, 5)
          .map((item) => `${item.city.name}, ${item.country.name}`)
          .join(", ");
        throw new Error(`City \"${target.city}\" is ambiguous. Specify --country. Matches: ${suggestions}.`);
      }
      country = globalMatches[0].country;
      city = globalMatches[0].city;
    }
  }

  return {
    country: country
      ? {
          id: country.id,
          code: country.code,
          name: country.name,
        }
      : null,
    city: city
      ? {
          id: city.id,
          name: city.name,
          latitude: city.latitude,
          longitude: city.longitude,
          dnsName: city.dns_name || null,
        }
      : null,
  };
}

async function fetchNordRecommendations(targetMeta, ipInfo) {
  const url = new URL("https://api.nordvpn.com/v1/servers/recommendations");
  const params = url.searchParams;
  params.append("limit", targetMeta.city ? "100" : "25");
  params.append("filters[servers.status]", "online");
  params.append("filters[servers_technologies]", "35");
  params.append("filters[servers_technologies][pivot][status]", "online");
  params.append("fields[servers.hostname]", "1");
  params.append("fields[servers.load]", "1");
  params.append("fields[servers.station]", "1");
  params.append("fields[servers.ips]", "1");
  params.append("fields[servers.technologies.identifier]", "1");
  params.append("fields[servers.technologies.metadata]", "1");
  params.append("fields[servers.locations.country.name]", "1");
  params.append("fields[servers.locations.country.city.name]", "1");

  if (targetMeta.country) {
    params.append("filters[country_id]", String(targetMeta.country.id));
  }

  const latitude = targetMeta.city ? targetMeta.city.latitude : ipInfo && ipInfo.latitude;
  const longitude = targetMeta.city ? targetMeta.city.longitude : ipInfo && ipInfo.longitude;
  if (Number.isFinite(Number(latitude)) && Number.isFinite(Number(longitude))) {
    params.append("coordinates[latitude]", String(latitude));
    params.append("coordinates[longitude]", String(longitude));
  }

  const result = await fetchJson(url.toString());
  if (!result.ok || result.statusCode >= 400 || !Array.isArray(result.json)) {
    throw new Error(result.error || `NordVPN server recommendation request failed (${result.statusCode || "unknown"})`);
  }
  return result.json;
}

function getServerCityName(server) {
  return (
    server &&
    Array.isArray(server.locations) &&
    server.locations[0] &&
    server.locations[0].country &&
    server.locations[0].country.city &&
    server.locations[0].country.city.name
  ) || "";
}

function getServerCountryName(server) {
  return (
    server &&
    Array.isArray(server.locations) &&
    server.locations[0] &&
    server.locations[0].country &&
    server.locations[0].country.name
  ) || "";
}

function getServerWireguardPublicKey(server) {
  const tech = (server.technologies || []).find((item) => item.identifier === "wireguard_udp");
  if (!tech) return "";
  const metadata = (tech.metadata || []).find((item) => item.name === "public_key");
  return metadata ? metadata.value : "";
}

function getServerIpAddresses(server) {
  return (server.ips || []).map((item) => item && item.ip && item.ip.ip).filter(Boolean);
}

function deriveClientIpv6(serverIpv6) {
  const parts = `${serverIpv6}`.split(":");
  if (parts.length < 4) return "";
  const expanded = [];
  let skipped = false;
  for (const part of parts) {
    if (!part && !skipped) {
      const missing = 8 - (parts.filter(Boolean).length);
      for (let i = 0; i < missing; i += 1) expanded.push("0000");
      skipped = true;
    } else if (part) {
      expanded.push(part.padStart(4, "0"));
    }
  }
  while (expanded.length < 8) expanded.push("0000");
  expanded[4] = "0000";
  expanded[5] = "0011";
  expanded[6] = "0005";
  expanded[7] = "0002";
  return expanded.map((item) => item.replace(/^0+(?=[0-9a-f])/i, "") || "0").join(":");
}

function buildWireguardConfig(server, privateKey) {
  const ipAddresses = getServerIpAddresses(server);
  const ipv6 = ipAddresses.find((value) => `${value}`.includes(":"));
  const addresses = [CLIENT_IPV4];
  const allowedIps = ["0.0.0.0/0"];

  if (ipv6) {
    const clientIpv6 = deriveClientIpv6(ipv6);
    if (clientIpv6) addresses.push(clientIpv6);
    allowedIps.push("::/0");
  }

  const publicKey = getServerWireguardPublicKey(server);
  if (!publicKey) {
    throw new Error(`Server ${server.hostname} does not expose a WireGuard public key.`);
  }

  return [
    "[Interface]",
    `PrivateKey = ${privateKey}`,
    `Address = ${addresses.join(", ")}`,
    "",
    "[Peer]",
    `PublicKey = ${publicKey}`,
    `AllowedIPs = ${allowedIps.join(", ")}`,
    `Endpoint = ${server.hostname}:51820`,
    "PersistentKeepalive = 25",
    "",
  ].join("\n");
}

function buildConnectTarget(args) {
  const country = normalizeLocation(args.country);
  const city = normalizeLocation(args.city);
  if (!country && !city) {
    throw new Error("connect requires --country, --city, or both.");
  }
  return { country, city };
}

function locationMatches(ipInfo, target) {
  if (!ipInfo || !ipInfo.ok) return false;
  const countryMatch = !target.country || normalizeForMatch(ipInfo.country).includes(normalizeForMatch(target.country));
  const cityMatch = !target.city || normalizeForMatch(ipInfo.city).includes(normalizeForMatch(target.city));
  return countryMatch && cityMatch;
}

async function verifyConnection(target) {
  const ipInfo = await getPublicIpInfo();
  return {
    ok: target ? locationMatches(ipInfo, target) : Boolean(ipInfo && ipInfo.ok),
    ipInfo,
  };
}

async function verifyConnectionWithRetry(target, options = {}) {
  const attempts = Math.max(1, Number(options.attempts || 4));
  const delayMs = Math.max(0, Number(options.delayMs || 1500));
  const getIpInfo = options.getPublicIpInfo || getPublicIpInfo;
  let lastIpInfo = null;

  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    const ipInfo = await getIpInfo();
    lastIpInfo = ipInfo;
    const ok = target ? locationMatches(ipInfo, target) : Boolean(ipInfo && ipInfo.ok);
    if (ok) {
      return { ok: true, ipInfo };
    }
    if (attempt < attempts) {
      await sleep(delayMs);
    }
  }

  return { ok: false, ipInfo: lastIpInfo };
}

async function connectViaCli(cliPath, target) {
  const attempts = [];
  if (target.city) attempts.push([target.city]);
  if (target.country) attempts.push([target.country]);
  if (target.country && target.city) attempts.push([`${target.country} ${target.city}`]);

  let lastFailure = "";
  for (const attemptArgs of attempts) {
    const result = await runExec(cliPath, ["connect", ...attemptArgs]);
    if (result.ok) {
      return { backend: "cli", cliTarget: attemptArgs.join(" "), raw: result.stdout.trim() || result.stderr.trim() };
    }
    lastFailure = (result.stderr || result.stdout || result.error).trim();
  }

  throw new Error(lastFailure || "NordVPN connect failed");
}

async function runSudoWireguard(installProbe, action) {
  const helperPath = installProbe.wireguard && installProbe.wireguard.helperPath;
  if (!helperPath) throw new Error(`WireGuard helper is missing at ${MAC_WG_HELPER_PATH}.`);
  if (!installProbe.wireguard.sudoReady) {
    throw new Error(`Non-interactive sudo is required for macOS WireGuard connect/disconnect. Allow sudo for ${MAC_WG_HELPER_PATH}, then rerun login/connect.`);
  }
  return runExec("sudo", ["-n", helperPath, action]);
}

async function connectViaMacWireguard(installProbe, target) {
  const token = readSecret("NORDVPN_TOKEN", "NORDVPN_TOKEN_FILE");
  if (!token) {
    throw new Error(`macOS NordLynx/WireGuard automation requires NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or a token at ${DEFAULT_TOKEN_FILE}.`);
  }
  if (!installProbe.wireguard || !installProbe.wireguard.dependenciesReady) {
    throw new Error("wireguard-go and wireguard-tools are required on macOS. Run install first.");
  }

  const ipInfo = await getPublicIpInfo();
  const countries = await fetchNordCountries();
  const targetMeta = resolveTargetMetadata(countries, target);
  if (!targetMeta.country) {
    throw new Error("A country is required to select a NordVPN WireGuard server.");
  }

  const credentials = await fetchNordCredentials(token);
  const recommendations = await fetchNordRecommendations(targetMeta, ipInfo);
  let candidates = recommendations.filter((server) => Boolean(getServerWireguardPublicKey(server)));
  if (targetMeta.city) {
    candidates = candidates.filter(
      (server) => normalizeForMatch(getServerCityName(server)) === normalizeForMatch(targetMeta.city.name)
    );
  }
  if (!candidates.length) {
    throw new Error(
      targetMeta.city
        ? `No WireGuard-capable NordVPN server found for ${targetMeta.city.name}, ${targetMeta.country.name}.`
        : `No WireGuard-capable NordVPN server found for ${targetMeta.country.name}.`
    );
  }

  candidates.sort((a, b) => (a.load || 999) - (b.load || 999));
  const selectedServer = candidates[0];
  const config = buildWireguardConfig(selectedServer, credentials.nordlynx_private_key);
  ensureDir(WG_STATE_DIR);
  writeTextFile(WG_CONFIG_PATH, config, 0o600);
  let tailscaleStopped = false;

  const tailscaleState = await stopMacTailscaleIfActive();
  tailscaleStopped = tailscaleState.tailscaleWasActive;

  const down = await runSudoWireguard(installProbe, "down");
  if (!down.ok) {
    // Ignore the common no-active-interface case before reconnecting.
  }

  const up = await runSudoWireguard(installProbe, "up");
  if (!up.ok) {
    if (tailscaleStopped) {
      await resumeMacTailscaleIfNeeded();
    }
    throw new Error((up.stderr || up.stdout || up.error).trim() || "wg-quick up failed");
  }

  return {
    backend: "wireguard",
    server: {
      hostname: selectedServer.hostname,
      city: getServerCityName(selectedServer),
      country: getServerCountryName(selectedServer),
      load: selectedServer.load,
    },
    requestedTarget: target,
    resolvedTarget: {
      country: targetMeta.country.name,
      city: targetMeta.city ? targetMeta.city.name : getServerCityName(selectedServer),
    },
    interfaceName: MAC_WG_INTERFACE,
    tailscaleStopped,
    raw: up.stdout.trim() || up.stderr.trim(),
  };
}

async function connectViaMacApp(target) {
  await openMacApp();
  return {
    backend: "app-manual",
    manualActionRequired: true,
    message: `Opened NordVPN.app. Connect manually to ${target.city ? `${target.city}, ` : ""}${target.country || "the requested location"} and rerun status/verify before the follow-up task.`,
  };
}

async function disconnectNordvpn(installProbe) {
  if (installProbe.cliPath) {
    const result = await runExec(installProbe.cliPath, ["disconnect"]);
    if (!result.ok) {
      throw new Error((result.stderr || result.stdout || result.error).trim() || "nordvpn disconnect failed");
    }
    return { backend: "cli", message: "Disconnected from NordVPN." };
  }

  if (installProbe.platform === "darwin" && installProbe.wireguard && installProbe.wireguard.dependenciesReady) {
    if (!shouldAttemptMacWireguardDisconnect(installProbe.wireguard)) {
      const dnsState = await restoreMacDnsIfNeeded();
      const tailscale = await resumeMacTailscaleIfNeeded();
      return {
        backend: "wireguard",
        changed: false,
        dnsRestored: dnsState.restored,
        tailscaleRestored: tailscale.restored,
        message: "No active macOS WireGuard NordVPN connection found.",
      };
    }
    const down = await runSudoWireguard(installProbe, "down");
    if (!down.ok) {
      const message = (down.stderr || down.stdout || down.error).trim();
      if (isBenignMacWireguardAbsentError(message)) {
        const dnsState = await restoreMacDnsIfNeeded();
        const cleaned = cleanupMacWireguardAndDnsState();
        const tailscale = await resumeMacTailscaleIfNeeded();
        return {
          backend: "wireguard",
          changed: false,
          stateCleaned: cleaned.cleaned,
          dnsRestored: dnsState.restored,
          tailscaleRestored: tailscale.restored,
          message: "No active macOS WireGuard NordVPN connection found.",
        };
      }
      throw new Error(message || "wg-quick down failed");
    }
    const dnsState = await restoreMacDnsIfNeeded();
    const cleaned = cleanupMacWireguardAndDnsState();
    const tailscale = await resumeMacTailscaleIfNeeded();
    return {
      backend: "wireguard",
      changed: true,
      stateCleaned: cleaned.cleaned,
      dnsRestored: dnsState.restored,
      tailscaleRestored: tailscale.restored,
      message: "Disconnected the macOS NordLynx/WireGuard session.",
    };
  }

  if (installProbe.platform === "darwin" && installProbe.appInstalled) {
    await openMacApp();
    return {
      backend: "app-manual",
      manualActionRequired: true,
      message: "Opened NordVPN.app. Disconnect manually because no automated control backend is available.",
    };
  }

  throw new Error("NordVPN is not installed.");
}

async function loginNordvpn(installProbe) {
  const token = readSecret("NORDVPN_TOKEN", "NORDVPN_TOKEN_FILE");
  const username = process.env.NORDVPN_USERNAME || "";
  const password = readSecret("NORDVPN_PASSWORD", "NORDVPN_PASSWORD_FILE");

  if (installProbe.cliPath) {
    if (token) {
      const result = await runExec(installProbe.cliPath, ["login", "--token", token]);
      if (!result.ok) {
        throw new Error((result.stderr || result.stdout || result.error).trim() || "nordvpn login --token failed");
      }
      return { mode: "cli-token", backend: "cli", message: "Logged in using token." };
    }

    if (username && password && installProbe.platform === "darwin") {
      // macOS CLI login support is not documented. Fall back to interactive CLI login.
    }

    const ok = await runInteractive(installProbe.cliPath, ["login"]);
    if (!ok) throw new Error("nordvpn login failed");
    return { mode: "cli-interactive", backend: "cli", message: "Interactive NordVPN login completed." };
  }

  if (installProbe.platform === "darwin" && token) {
    const credentials = await fetchNordCredentials(token);
    const cache = {
      backend: "wireguard",
      validatedAt: new Date().toISOString(),
      hasNordlynxPrivateKey: Boolean(credentials.nordlynx_private_key),
      tokenSource: process.env.NORDVPN_TOKEN
        ? "env:NORDVPN_TOKEN"
        : process.env.NORDVPN_TOKEN_FILE
          ? "file:NORDVPN_TOKEN_FILE"
          : fileExists(DEFAULT_TOKEN_FILE)
            ? `default:${DEFAULT_TOKEN_FILE}`
            : "unknown",
    };
    writeJsonFile(AUTH_CACHE_PATH, cache);
    return {
      mode: "wireguard-token",
      backend: "wireguard",
      message: installProbe.wireguard && installProbe.wireguard.dependenciesReady
        ? "Validated NordVPN token for the macOS NordLynx/WireGuard backend."
        : "Validated NordVPN token. Run install to bootstrap wireguard-go and wireguard-tools before connecting.",
      auth: cache,
    };
  }

  if (installProbe.platform === "darwin" && installProbe.appInstalled) {
    await openMacApp();
    return {
      mode: "app-manual",
      backend: "app-manual",
      manualActionRequired: true,
      message:
        `Opened NordVPN.app. Complete login in the app/browser flow, or set NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or place the token at ${DEFAULT_TOKEN_FILE} to use the automated macOS WireGuard backend.`,
    };
  }

  throw new Error("NordVPN is not installed.");
}

async function main() {
  const args = parseArgs(process.argv.slice(2));
  const debugOutput = Boolean(args.debug);
  const emitJson = (payload, exitCode = 0, errorStream = false) => printJson(payload, exitCode, errorStream, debugOutput);
  const action = args._[0];
  if (!action || args.help) {
    emitJson(usage(), action ? 0 : 1, !action);
  }

  const platform = detectPlatform();
  if (!["darwin", "linux"].includes(platform)) {
    emitJson({ error: `Unsupported platform: ${platform}` }, 1, true);
  }

  const installProbe = await probeInstallation(platform);

  try {
    if (action === "status") {
      const ipInfo = await getPublicIpInfo();
      emitJson(normalizeStatusState(buildStateSummary(installProbe, ipInfo)));
    }

    if (action === "install") {
      const result = await installNordvpn(installProbe);
      const refreshed = await probeInstallation(platform);
      emitJson({
        action,
        ...result,
        state: buildStateSummary(refreshed, await getPublicIpInfo()),
      });
    }

    if (action === "login") {
      const result = await loginNordvpn(installProbe);
      const refreshed = await probeInstallation(platform);
      emitJson({
        action,
        ...result,
        state: buildStateSummary(refreshed, await getPublicIpInfo()),
      });
    }

    if (action === "verify") {
      const target = args.country || args.city ? buildConnectTarget(args) : null;
      const verified = await verifyConnectionWithRetry(target);
      const refreshed = await probeInstallation(platform);
      emitJson(
        {
          action,
          requestedTarget: target,
          verified: verified.ok,
          verification: verified.ipInfo,
          state: buildStateSummary(refreshed, verified.ipInfo),
        },
        verified.ok ? 0 : 1,
        !verified.ok
      );
    }

    if (action === "connect") {
      const lock = acquireOperationLock(action);
      try {
        const target = buildConnectTarget(args);
        let connectResult;
        let verified;
        let persistence = null;

        if (installProbe.cliPath) {
          connectResult = await connectViaCli(installProbe.cliPath, target);
        } else if (platform === "darwin" && installProbe.wireguard && installProbe.wireguard.dependenciesReady && installProbe.tokenAvailable) {
          connectResult = await connectViaMacWireguard(installProbe, target);
        } else if (platform === "darwin" && installProbe.appInstalled) {
          connectResult = await connectViaMacApp(target);
        } else if (platform === "darwin" && !installProbe.tokenAvailable) {
          throw new Error(`macOS automated NordLynx/WireGuard connects require NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or a token at ${DEFAULT_TOKEN_FILE}.`);
        } else if (platform === "darwin") {
          throw new Error("No usable macOS NordVPN backend is ready. Run install to bootstrap WireGuard tooling.");
        } else if (!installProbe.installed) {
          throw new Error("NordVPN is not installed.");
        } else {
          throw new Error("No usable NordVPN control path found.");
        }

        if (connectResult.manualActionRequired) {
          lock.release();
          emitJson({ action, requestedTarget: target, ...connectResult });
        }

        if (connectResult && connectResult.backend === "wireguard" && platform === "darwin") {
          persistence = await checkMacWireguardPersistence(target, {
            attempts: CONNECT_PERSISTENCE_ATTEMPTS,
            delayMs: CONNECT_PERSISTENCE_DELAY_MS,
            getHelperStatus: async () => getMacWireguardHelperStatus(installProbe),
            verifyConnection: verifyConnection,
          });
          verified = persistence.verified;

          if (!persistence.stable) {
            const refreshed = await probeInstallation(platform);
            const diagnostics = await collectMacWireguardDiagnostics({
              interfaceName: connectResult.interfaceName || MAC_WG_INTERFACE,
              wgPath: refreshed.wireguard && refreshed.wireguard.wgPath,
              helperPath: refreshed.wireguard && refreshed.wireguard.helperPath,
              helperSecurity: refreshed.wireguard && refreshed.wireguard.helperSecurity,
            });
            const rollback = await disconnectNordvpn(refreshed);
            lock.release();
            emitJson(
              {
                action,
                requestedTarget: target,
                connectResult,
                persistence,
                verified: false,
                verification: verified && verified.ipInfo ? verified.ipInfo : null,
                diagnostics: debugOutput ? diagnostics : undefined,
                diagnosticsSummary: summarizeMacWireguardDiagnostics(diagnostics),
                rollback,
                state: buildStateSummary(await probeInstallation(platform), await getPublicIpInfo()),
              },
              1,
              true
            );
          }

          verified = await verifyConnection(target);
        } else {
          verified = await verifyConnectionWithRetry(target, {
            attempts: CONNECT_PERSISTENCE_ATTEMPTS,
            delayMs: CONNECT_PERSISTENCE_DELAY_MS,
          });
        }

        const refreshed = await probeInstallation(platform);
        if (!verified.ok && connectResult && connectResult.backend === "wireguard" && platform === "darwin") {
          const diagnostics = await collectMacWireguardDiagnostics({
            interfaceName: connectResult.interfaceName || MAC_WG_INTERFACE,
            wgPath: refreshed.wireguard && refreshed.wireguard.wgPath,
            helperPath: refreshed.wireguard && refreshed.wireguard.helperPath,
            helperSecurity: refreshed.wireguard && refreshed.wireguard.helperSecurity,
          });
          const rollback = await disconnectNordvpn(refreshed);
          lock.release();
          emitJson(
            {
              action,
              requestedTarget: target,
              connectResult,
              persistence,
              verified: false,
              verification: verified.ipInfo,
              diagnostics: debugOutput ? diagnostics : undefined,
              diagnosticsSummary: summarizeMacWireguardDiagnostics(diagnostics),
              rollback,
              state: buildStateSummary(await probeInstallation(platform), await getPublicIpInfo()),
            },
            1,
            true
          );
        }

        if (platform === "darwin" && shouldFinalizeMacWireguardConnect(connectResult, verified, persistence)) {
          try {
            await snapshotMacDnsState();
            const liveness = await verifyConnection(target);
            if (!liveness.ok) {
              const rollback = await disconnectNordvpn(await probeInstallation(platform));
              lock.release();
              emitJson(
                {
                  action,
                  requestedTarget: target,
                  connectResult,
                  persistence,
                  verified: false,
                  verification: liveness.ipInfo,
                  rollback,
                  error: "Connected but failed the final liveness check before DNS finalization.",
                  state: buildStateSummary(await probeInstallation(platform), await getPublicIpInfo()),
                },
                1,
                true
              );
            }
            verified = liveness;
            await applyMacNordDns();
            const dnsResolution = await verifySystemHostnameResolution();
            if (!dnsResolution.ok) {
              const rollback = await disconnectNordvpn(await probeInstallation(platform));
              lock.release();
              emitJson(
                {
                  action,
                  requestedTarget: target,
                  connectResult,
                  persistence,
                  verified: false,
                  verification: verified.ipInfo,
                  dnsResolution,
                  rollback,
                  error: `Connected but system DNS resolution failed after DNS finalization: ${dnsResolution.error}`,
                  state: buildStateSummary(await probeInstallation(platform), await getPublicIpInfo()),
                },
                1,
                true
              );
            }
            writeJsonFile(LAST_CONNECTION_PATH, {
              backend: "wireguard",
              interfaceName: MAC_WG_INTERFACE,
              requestedTarget: connectResult.requestedTarget,
              resolvedTarget: connectResult.resolvedTarget,
              server: {
                hostname: connectResult.server.hostname,
                city: connectResult.server.city,
                country: connectResult.server.country,
                load: connectResult.server.load,
              },
              connectedAt: new Date().toISOString(),
            });
          } catch (error) {
            const rollback = await disconnectNordvpn(await probeInstallation(platform));
            lock.release();
            emitJson(
              {
                action,
                requestedTarget: target,
                connectResult,
                persistence,
                verified: true,
                verification: verified.ipInfo,
                rollback,
                error: `Connected but failed to finalize macOS DNS/state: ${error.message || String(error)}`,
                state: buildStateSummary(await probeInstallation(platform), await getPublicIpInfo()),
              },
              1,
              true
            );
          }
        }

        const connectState = normalizeSuccessfulConnectState(
          buildStateSummary(await probeInstallation(platform), verified.ipInfo),
          connectResult,
          verified
        );
        lock.release();
        emitJson(
          {
            action,
            requestedTarget: target,
            connectResult,
            persistence,
            verified: verified.ok,
            verification: verified.ipInfo,
            state: connectState,
          },
          verified.ok ? 0 : 1,
          !verified.ok
        );
      } finally {
        lock.release();
      }
    }

    if (action === "disconnect") {
      const lock = acquireOperationLock(action);
      try {
        const result = await disconnectNordvpn(installProbe);
        const refreshed = await probeInstallation(platform);
        lock.release();
        emitJson({
          action,
          ...result,
          state: buildStateSummary(refreshed, await getPublicIpInfo()),
        });
      } finally {
        lock.release();
      }
    }

    emitJson({ error: `Unknown action: ${action}`, ...usage() }, 1, true);
  } catch (error) {
    const payload = { error: error.message || String(error), action };
    if (action === "connect" && platform === "darwin" && installProbe && installProbe.wireguard) {
      try {
        const refreshed = await probeInstallation(platform);
        const diagnostics = await collectMacWireguardDiagnostics({
          interfaceName: MAC_WG_INTERFACE,
          wgPath: refreshed.wireguard && refreshed.wireguard.wgPath,
          helperPath: refreshed.wireguard && refreshed.wireguard.helperPath,
          helperSecurity: refreshed.wireguard && refreshed.wireguard.helperSecurity,
        });
        payload.state = buildStateSummary(refreshed, await getPublicIpInfo());
        payload.diagnostics = debugOutput ? diagnostics : undefined;
        payload.diagnosticsSummary = summarizeMacWireguardDiagnostics(diagnostics);
      } catch {
        // Fall back to the base error payload if diagnostic capture also fails.
      }
    }
    emitJson(payload, 1, true);
  }
}

main();
