import { createHash } from "node:crypto";

const DOCUMENTED_UPSTREAM_PATH_ARTIFACTS = Object.freeze({
  "engines/perl/emperl.data":
    "9529019418cf766a42cf2d25bd3fc97b47c9e689f5666cfc32dc11338d1b1e66",
  "engines/ruby/ruby.wasm":
    "fa1d5af1b4d601191739e6c4980d060d0d202181a7c83abf4930b3ca35b90f94",
});

const LOCAL_PATH_PREFIXES = Object.freeze([
  "/mnt/",
  "/Users/",
  "/home/",
  ...Array.from(
    { length: 26 },
    (_, index) => `${String.fromCharCode(65 + index)}:\\Users\\`,
  ),
  ...Array.from(
    { length: 26 },
    (_, index) => `${String.fromCharCode(65 + index)}:/Users/`,
  ),
  ...Array.from(
    { length: 26 },
    (_, index) => `${String.fromCharCode(97 + index)}:\\Users\\`,
  ),
  ...Array.from(
    { length: 26 },
    (_, index) => `${String.fromCharCode(97 + index)}:/Users/`,
  ),
]);

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

function utf16BigEndian(value) {
  const encoded = Buffer.from(value, "utf16le");
  for (let index = 0; index < encoded.byteLength; index += 2) {
    const first = encoded[index];
    encoded[index] = encoded[index + 1];
    encoded[index + 1] = first;
  }
  return encoded;
}

function encodedForms(value) {
  return [
    { encoding: "ASCII/UTF-8", value: Buffer.from(value, "utf8") },
    { encoding: "UTF-16LE", value: Buffer.from(value, "utf16le") },
    { encoding: "UTF-16BE", value: utf16BigEndian(value) },
  ];
}

function startsWithAt(data, value, offset) {
  return (
    offset + value.byteLength <= data.byteLength &&
    data.subarray(offset, offset + value.byteLength).equals(value)
  );
}

function isAllowedVirtualHome(data, offset, encoding, allowedVirtualHomes) {
  return allowedVirtualHomes.some((home) => {
    const encodedHome = encodedForms(home).find(
      (candidate) => candidate.encoding === encoding,
    )?.value;
    if (!encodedHome || !startsWithAt(data, encodedHome, offset)) return false;
    const end = offset + encodedHome.byteLength;
    if (end === data.byteLength) return true;
    const character =
      encoding === "UTF-16LE" && end + 1 < data.byteLength
        ? data.readUInt16LE(end)
        : encoding === "UTF-16BE" && end + 1 < data.byteLength
          ? data.readUInt16BE(end)
          : data[end];
    return (
      character === 0 ||
      character === 0x2f ||
      !(
        (character >= 0x30 && character <= 0x39) ||
        (character >= 0x41 && character <= 0x5a) ||
        (character >= 0x61 && character <= 0x7a) ||
        character === 0x2e ||
        character === 0x5f ||
        character === 0x2d
      )
    );
  });
}

export function localAbsolutePathMatch(
  input,
  artifactName,
  { allowedVirtualHomes = [] } = {},
) {
  const data = Buffer.from(input);
  const documentedDigest = DOCUMENTED_UPSTREAM_PATH_ARTIFACTS[artifactName];
  if (documentedDigest && sha256(data) === documentedDigest) return null;

  for (const prefix of LOCAL_PATH_PREFIXES) {
    for (const encoded of encodedForms(prefix)) {
      let offset = data.indexOf(encoded.value);
      while (offset !== -1) {
        if (
          prefix !== "/home/" ||
          !isAllowedVirtualHome(
            data,
            offset,
            encoded.encoding,
            allowedVirtualHomes,
          )
        ) {
          return { prefix, encoding: encoded.encoding };
        }
        offset = data.indexOf(encoded.value, offset + encoded.value.byteLength);
      }
    }
  }
  return null;
}

export function assertNoUnexpectedLocalPaths(
  input,
  artifactName,
  options = {},
) {
  const match = localAbsolutePathMatch(input, artifactName, options);
  if (match) {
    throw new Error(
      `Local absolute path detected in ${artifactName} (${match.encoding} ${match.prefix}).`,
    );
  }
}
