/**
 * Step 1 — bootstrap directory URL list for APK/EXE clients.
 * Full live list is fetched from GET /v1/directory (signed) and cached locally.
 * Primary = direct VPS :9334; HTTPS = same host via nginx when :9334 is blocked.
 */
const fs = require("fs");
const path = require("path");
const { verifyDirectoryDoc } = require("./release_verify");

function normalizeList(raw) {
  const out = [];
  const seen = new Set();
  for (const item of raw) {
    const u = String(item || "")
      .trim()
      .replace(/\/$/, "");
    if (!u || seen.has(u)) continue;
    if (!/^https?:\/\//i.test(u)) continue;
    seen.add(u);
    out.push(u);
  }
  return out;
}

const DEFAULT_DIRECTORY_URLS = [
  "http://78.47.21.219:9334",
  "http://78.47.21.219:9335",
  "https://sarmad.pro",
  "https://www.sarmad.pro",
];

/**
 * Env overrides:
 * - SARMAD_DIRECTORY_URL = force primary (tried first)
 * - SARMAD_DIRECTORY_URLS = comma-separated full list (replaces defaults when set)
 */
function directoryUrls() {
  const fromEnvList = String(process.env.SARMAD_DIRECTORY_URLS || "")
    .split(",")
    .map((s) => s.trim())
    .filter(Boolean);
  if (fromEnvList.length > 0) return normalizeList(fromEnvList);
  const primary = String(process.env.SARMAD_DIRECTORY_URL || "").replace(/\/$/, "");
  if (primary) return normalizeList([primary, ...DEFAULT_DIRECTORY_URLS]);
  return normalizeList(DEFAULT_DIRECTORY_URLS);
}

function cachePath() {
  const dir =
    process.env.SARMAD_DIRECTORY_CACHE_DIR ||
    process.env.SARMAD_DATA_DIR ||
    path.join(require("os").homedir(), ".sarmad");
  try {
    fs.mkdirSync(dir, { recursive: true });
  } catch (_) {}
  return path.join(dir, "vps_directory.json");
}

function stickyPath() {
  const dir =
    process.env.SARMAD_DIRECTORY_CACHE_DIR ||
    process.env.SARMAD_DATA_DIR ||
    path.join(require("os").homedir(), ".sarmad");
  try {
    fs.mkdirSync(dir, { recursive: true });
  } catch (_) {}
  return path.join(dir, "sticky_coordinator.txt");
}

function loadSticky() {
  try {
    const t = fs.readFileSync(stickyPath(), "utf8").trim().replace(/\/$/, "");
    if (/^https?:\/\//i.test(t)) return t;
  } catch (_) {}
  return "";
}

function saveSticky(url) {
  try {
    fs.writeFileSync(stickyPath(), String(url || "").replace(/\/$/, ""));
  } catch (_) {}
}

function clearSticky() {
  try {
    fs.unlinkSync(stickyPath());
  } catch (_) {}
}

function loadCachedDirectory() {
  try {
    if (!fs.existsSync(cachePath())) return null;
    return JSON.parse(fs.readFileSync(cachePath(), "utf8"));
  } catch (_) {
    return null;
  }
}

function saveCachedDirectory(doc) {
  try {
    fs.writeFileSync(cachePath(), JSON.stringify(doc, null, 2));
  } catch (_) {}
}

function getJson(url, timeoutMs = 8000) {
  const http = require("http");
  const https = require("https");
  return new Promise((resolve, reject) => {
    const lib = url.startsWith("https") ? https : http;
    const req = lib.get(url, { timeout: timeoutMs }, (res) => {
      if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
        getJson(res.headers.location, timeoutMs).then(resolve, reject);
        res.resume();
        return;
      }
      const chunks = [];
      res.on("data", (c) => chunks.push(c));
      res.on("end", () => {
        if (!res.statusCode || res.statusCode >= 400) {
          reject(new Error("HTTP " + res.statusCode));
          return;
        }
        try {
          resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
        } catch (e) {
          reject(e);
        }
      });
    });
    req.on("error", reject);
    req.on("timeout", () => {
      req.destroy();
      reject(new Error("timeout"));
    });
  });
}

async function refreshSignedDirectory(log = console.log) {
  const seeds = directoryUrls();
  const cached = loadCachedDirectory();
  let urls = seeds.slice();
  if (cached) {
    const v = await verifyDirectoryDoc(cached);
    if (v.ok) urls = normalizeList(v.urls);
  }
  const probe = normalizeList([...urls, ...seeds]);
  for (const base of probe) {
    try {
      const doc = await getJson(base + "/v1/directory");
      const v = await verifyDirectoryDoc(doc);
      if (!v.ok) {
        log(`[directory] ${base}/v1/directory rejected: ${v.error}`);
        continue;
      }
      saveCachedDirectory(doc);
      log(`[directory] signed list OK from ${base} (${v.urls.length} urls)`);
      return v.urls;
    } catch (e) {
      log(`[directory] ${base}/v1/directory failed: ${e && e.message ? e.message : e}`);
    }
  }
  if (cached) {
    const v = await verifyDirectoryDoc(cached);
    if (v.ok) {
      log(`[directory] using cached signed list (${v.urls.length} urls)`);
      return v.urls;
    }
  }
  log(`[directory] falling back to bootstrap seeds (${seeds.length})`);
  return seeds;
}

function queueLength(status) {
  const q = Number(status && status.queueLength);
  if (Number.isFinite(q) && q > 0) return q;
  return Math.max(0, Number(status && status.activeCount) || 0);
}

async function clusterBindUrl(homeUrl, status, log = console.log) {
  const home = String(homeUrl || "").replace(/\/$/, "");
  if (!status || typeof status !== "object") return { url: home, label: "sticky", status };
  if (status.clusterEnabled && !status.isLeader && status.leaderUrl) {
    const leader = String(status.leaderUrl).replace(/\/$/, "");
    if (leader && leader !== home) {
      log(`[directory] cluster follower home ${home} — using leader ${leader}`);
      try {
        const leaderStatus = await getJson(leader + "/v1/status");
        return { url: leader, label: "cluster-leader", status: leaderStatus || status };
      } catch (e) {
        log(`[directory] leader ${leader} status failed: ${e && e.message ? e.message : e}`);
        return { url: leader, label: "cluster-leader", status };
      }
    }
  }
  return { url: home, label: "sticky", status };
}

/**
 * Sticky home + shortest-queue among signed/bootstrap URLs (matches Android).
 * @returns {{ url: string, index: number, label: string, status: object|null }}
 */
async function resolveDirectory(log = console.log) {
  const urls = await refreshSignedDirectory(log);
  const sticky = loadSticky();
  if (sticky) {
    try {
      const status = await getJson(sticky + "/v1/status");
      if (status && typeof status === "object") {
        const index = Math.max(0, urls.indexOf(sticky));
        const label = index === 0 ? "sticky-primary" : "sticky-" + index;
        const bound = await clusterBindUrl(sticky, status, log);
        log(`[directory] using ${bound.url} (${bound.label === "cluster-leader" ? bound.label : label})`);
        return {
          url: bound.url,
          homeUrl: sticky,
          index,
          label: bound.label === "cluster-leader" ? bound.label : label,
          status: bound.status,
        };
      }
    } catch (e) {
      log(`[directory] sticky ${sticky} failed: ${e && e.message ? e.message : e}`);
      clearSticky();
    }
  }

  const probes = [];
  for (let i = 0; i < urls.length; i++) {
    const url = urls[i];
    try {
      const status = await getJson(url + "/v1/status");
      if (status && typeof status === "object") {
        probes.push({ url, index: i, status });
      }
    } catch (e) {
      log(`[directory] ${url} failed: ${e && e.message ? e.message : e}`);
    }
  }

  if (probes.length === 0) {
    const fallback = urls[0] || DEFAULT_DIRECTORY_URLS[0];
    log(`[directory] all unreachable — last resort ${fallback}`);
    return { url: fallback, homeUrl: fallback, index: 0, label: "primary (unreachable)", status: null };
  }

  probes.sort((a, b) => {
    const dq = queueLength(a.status) - queueLength(b.status);
    if (dq !== 0) return dq;
    if (a.index !== b.index) return a.index - b.index;
    return a.url.localeCompare(b.url);
  });
  const best = probes[0];
  const bound = await clusterBindUrl(best.url, best.status, log);
  const label =
    bound.label === "cluster-leader"
      ? bound.label
      : probes.length === 1
        ? best.index === 0
          ? "primary"
          : "backup-" + best.index
        : "shortest-queue";
  saveSticky(best.url);
  log(`[directory] using ${bound.url} (${label})`);
  return { url: bound.url, homeUrl: best.url, index: best.index, label, status: bound.status };
}

module.exports = {
  DEFAULT_DIRECTORY_URLS,
  directoryUrls,
  resolveDirectory,
  refreshSignedDirectory,
  loadSticky,
  saveSticky,
  clearSticky,
};
