#!/usr/bin/env node
"use strict";

const fs = require("fs");
const os = require("os");
const path = require("path");
const childProcess = require("child_process");

const TOOL_NAME = "dbspec";
const ROOT_PACKAGE_NAME = "@ai-devtools/dbspec";
const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
const UPDATE_CHECK_TIMEOUT_MS = 1000;
const INVENTORY_CANDIDATES = [
  path.join(__dirname, "..", "inventory.json"),
  path.join(__dirname, "..", "..", "inventory.json"),
];

function loadInventory() {
  for (const candidate of INVENTORY_CANDIDATES) {
    if (fs.existsSync(candidate)) {
      return JSON.parse(fs.readFileSync(candidate, "utf8"));
    }
  }
  throw new Error("DBSpec package inventory was not found.");
}

function runtimePackages(inventory) {
  return inventory.platform_packages.filter((target) => target.runtime_dependency);
}

function supportedTargets(inventory) {
  return runtimePackages(inventory).map((target) => target.target);
}

function resolveTarget(platform = process.platform, arch = process.arch) {
  const inventory = loadInventory();
  const target = runtimePackages(inventory).find(
    (candidate) => candidate.node_platform === platform && candidate.node_arch === arch
  );
  if (target) {
    return target;
  }

  const error = new Error(
    `Unsupported platform target: ${platform}/${arch}. Supported targets: ${supportedTargets(inventory).join(", ")}.`
  );
  error.code = "DBSPEC_UNSUPPORTED_PLATFORM";
  throw error;
}

function resolveBinaryPath(target) {
  const manifestPath = require.resolve(`${target.package}/package.json`);
  return path.join(path.dirname(manifestPath), target.binary_path);
}

function rootPackageJsonPath() {
  return path.join(__dirname, "..", "package.json");
}

function rootPackageVersion() {
  const packageJsonPath = rootPackageJsonPath();
  if (!fs.existsSync(packageJsonPath)) {
    return null;
  }
  const manifest = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
  return typeof manifest.version === "string" ? manifest.version : null;
}

function cacheRoot() {
  if (process.platform === "win32" && process.env.LOCALAPPDATA) {
    return path.join(process.env.LOCALAPPDATA, "ai-devtools");
  }
  if (process.env.XDG_CACHE_HOME) {
    return path.join(process.env.XDG_CACHE_HOME, "ai-devtools");
  }
  return path.join(os.homedir(), ".cache", "ai-devtools");
}

function updateCheckCachePath(toolName = TOOL_NAME) {
  return path.join(cacheRoot(), `${toolName}-update-check.json`);
}

function parseVersion(version) {
  const match = String(version || "").trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
  if (!match) {
    return null;
  }
  return match.slice(1, 4).map((part) => Number.parseInt(part, 10));
}

function compareVersions(left, right) {
  const leftParts = parseVersion(left);
  const rightParts = parseVersion(right);
  if (!leftParts || !rightParts) {
    return 0;
  }
  for (let index = 0; index < leftParts.length; index += 1) {
    if (leftParts[index] !== rightParts[index]) {
      return leftParts[index] - rightParts[index];
    }
  }
  return 0;
}

function readUpdateCache(cachePath, nowMs = Date.now()) {
  try {
    const payload = JSON.parse(fs.readFileSync(cachePath, "utf8"));
    if (typeof payload.checkedAt !== "number") {
      return null;
    }
    if (nowMs - payload.checkedAt > UPDATE_CHECK_TTL_MS) {
      return null;
    }
    return typeof payload.latestVersion === "string" ? payload.latestVersion : null;
  } catch {
    return null;
  }
}

function writeUpdateCache(cachePath, latestVersion, nowMs = Date.now()) {
  try {
    fs.mkdirSync(path.dirname(cachePath), { recursive: true });
    fs.writeFileSync(
      cachePath,
      JSON.stringify({ checkedAt: nowMs, latestVersion }, null, 2),
      "utf8"
    );
  } catch {
    // The update notice is best-effort and must never block CLI startup.
  }
}

function latestPackageVersion(packageName, spawnSync = childProcess.spawnSync) {
  const result = spawnSync("npm", ["view", packageName, "version", "--silent"], {
    encoding: "utf8",
    timeout: UPDATE_CHECK_TIMEOUT_MS,
    stdio: ["ignore", "pipe", "ignore"],
  });
  if (result.error || result.status !== 0 || typeof result.stdout !== "string") {
    return null;
  }
  const latestVersion = result.stdout.trim();
  return parseVersion(latestVersion) ? latestVersion : null;
}

function maybePrintUpdateNotice(options = {}) {
  const stderrIsTTY =
    typeof options.stderrIsTTY === "boolean" ? options.stderrIsTTY : Boolean(process.stderr.isTTY);
  const isCI = typeof options.isCI === "boolean" ? options.isCI : process.env.CI === "true";
  if (!stderrIsTTY || isCI) {
    return;
  }

  const toolName = options.toolName || TOOL_NAME;
  const packageName = options.packageName || ROOT_PACKAGE_NAME;
  const currentVersion = options.currentVersion || rootPackageVersion();
  if (!currentVersion) {
    return;
  }

  const nowMs = options.nowMs || Date.now();
  const cachePath = options.cachePath || updateCheckCachePath(toolName);
  const cachedLatestVersion = readUpdateCache(cachePath, nowMs);
  const latestVersion =
    cachedLatestVersion ||
    latestPackageVersion(packageName, options.spawnSync || childProcess.spawnSync);
  if (!cachedLatestVersion && latestVersion) {
    writeUpdateCache(cachePath, latestVersion, nowMs);
  }

  if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
    const writeNotice = options.writeNotice || console.error;
    writeNotice(`A newer ${toolName} version is available: ${currentVersion} -> ${latestVersion}`);
    writeNotice(`Update: npm install -g ${packageName}@latest`);
  }
}

function run(argv = process.argv.slice(2)) {
  maybePrintUpdateNotice();
  const target = resolveTarget();
  const binaryPath = resolveBinaryPath(target);
  const result = childProcess.spawnSync(binaryPath, argv, { stdio: "inherit" });
  if (result.error) {
    throw result.error;
  }
  if (result.signal) {
    process.kill(process.pid, result.signal);
    return 1;
  }
  return result.status === null ? 1 : result.status;
}

if (require.main === module) {
  try {
    process.exit(run());
  } catch (error) {
    console.error(error.message);
    process.exit(1);
  }
}

module.exports = {
  compareVersions,
  latestPackageVersion,
  loadInventory,
  maybePrintUpdateNotice,
  resolveTarget,
  run,
  supportedTargets,
  updateCheckCachePath,
};
