#!/usr/bin/env node

import { createHash } from "node:crypto";
import { lstat, readFile, realpath, stat } from "node:fs/promises";
import path from "node:path";

import { scan } from "./npm-supply-chain.mjs";

function value(args, index, name) {
  const item = args[index + 1];
  if (!item || item.startsWith("--")) throw new Error(`${name} requires a value.`);
  return item;
}

function parse(argv) {
  const [capability, ...args] = argv;
  const options = {};
  for (let index = 0; index < args.length; index += 1) {
    const name = args[index];
    if (!["--workspace", "--home", "--feed-file", "--relative-path"].includes(name)) {
      throw new Error(`Unsupported option: ${name}`);
    }
    options[name.slice(2).replaceAll("-", "_")] = value(args, index++, name);
  }
  return { capability, options };
}

function digest(value) {
  return createHash("sha256").update(JSON.stringify(value)).digest("hex");
}

async function directory(value, label) {
  const resolved = await realpath(path.resolve(value || ""));
  if (!(await stat(resolved)).isDirectory()) throw new Error(`${label} must be a directory.`);
  return resolved;
}

export async function npmSupplyChain(options) {
  const workspace = await directory(options.workspace, "workspace");
  const home = await directory(options.home, "home");
  const feedPath = path.resolve(options.feed_file || "");
  const feedMeta = await lstat(feedPath);
  if (!feedMeta.isFile() || feedMeta.isSymbolicLink()) throw new Error("feed-file must be a regular file.");
  const feedBytes = await readFile(feedPath);
  const raw = await scan({ roots: [workspace], home, feedFile: feedPath, feedUrl: null });
  const result = {
    type: "acuity.npm-supply-chain-assessment.v1", capabilityId: "npm_supply_chain_assessment",
    generatedAt: raw.generatedAt, campaign: raw.campaign,
    feed: { sha256: createHash("sha256").update(feedBytes).digest("hex"), artifactCount: raw.feedArtifacts },
    coverage: raw.coverage,
    indicators: { packageHits: raw.packageHits.length, installedHits: raw.installedHits.length, fileHits: raw.fileHits.length, hookHits: raw.hookHits.length, persistenceHits: raw.persistenceHits.length, total: raw.compromiseIndicators },
    errors: raw.errors.length, ok: raw.ok, source: { workspace: "paired-workspace", home: "paired-home" },
  };
  return { ...result, reportDigest: digest(result) };
}

export async function sensitiveMode(options) {
  const workspace = await directory(options.workspace, "workspace");
  const relativePath = String(options.relative_path || "").replaceAll("\\", "/");
  if (!relativePath || path.posix.isAbsolute(relativePath) || relativePath.split("/").some((part) => !part || part === "." || part === "..")) throw new Error("relative-path must be an exact non-traversing relative path.");
  const target = path.resolve(workspace, relativePath);
  const parent = await realpath(path.dirname(target));
  if (parent !== workspace && !parent.startsWith(`${workspace}${path.sep}`)) throw new Error("relative-path resolves through a workspace-external parent.");
  const metadata = await lstat(target);
  if (!metadata.isFile() || metadata.isSymbolicLink()) throw new Error("Assessment requires a regular non-symlink file.");
  const mode = metadata.mode & 0o777;
  return { type: "acuity.sensitive-file-mode-assessment.v1", capabilityId: "sensitive_file_mode_assessment", mode: "read_only", relativePath, modeOctal: `0${mode.toString(8).padStart(3, "0")}`, uid: metadata.uid, gid: metadata.gid, ownerOnly: (mode & 0o077) === 0 };
}

async function main() {
  const { capability, options } = parse(process.argv.slice(2));
  const output = capability === "npm-supply-chain" ? await npmSupplyChain(options)
    : capability === "sensitive-file-mode" ? await sensitiveMode(options)
      : (() => { throw new Error(`Unsupported Acuity capability: ${capability}`); })();
  process.stdout.write(`${JSON.stringify(output)}\n`);
}

if (import.meta.url === `file://${process.argv[1]}`) main().catch((error) => { process.stderr.write(`${error.message}\n`); process.exitCode = 2; });
