import { createHash } from "node:crypto";
import {
  closeSync,
  constants,
  fstatSync,
  openSync,
  readFileSync,
} from "node:fs";
import { basename, dirname, isAbsolute, join, resolve } from "node:path";

import { validateRunDispatchV1 } from "./runtime-dispatch.mjs";
import { canonicalJson } from "./runtime-release.mjs";

const STAGE_PREFIX = "overdeck-runtime-account.";
const UNIT_PREFIX = "overdeck-runtime-account-gc-";
const SUFFIX = /^[a-f0-9]{32}$/;
const SLUG = /^[a-z0-9][a-z0-9._-]{0,127}$/;
const SHA256 = /^[a-f0-9]{64}$/;
const TTL_SECONDS = 600;

function shellQuote(value) {
  return `'${String(value).replace(/'/g, `'\\''`)}'`;
}

function control(value) {
  return Array.from(value).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127);
}

function readPrivateRegular(path, label, fstat) {
  let fd;
  try {
    fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
    const stat = fstat(fd);
    if (!stat.isFile()) throw new Error(`${label} must be a regular file`);
    if ((stat.mode & 0o7177) !== 0) throw new Error(`${label} mode must not be broader than 0600`);
    return { bytes: readFileSync(fd), stat };
  } catch (error) {
    if (error?.message?.startsWith(`${label} `)) throw error;
    throw new Error(`${label} unavailable`);
  } finally {
    if (fd !== undefined) closeSync(fd);
  }
}

function parseJson(bytes, label) {
  try {
    return JSON.parse(bytes.toString("utf8"));
  } catch {
    throw new Error(`${label} malformed`);
  }
}

function plain(value) {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}

function validateSuffix(suffix) {
  if (typeof suffix !== "string" || !SUFFIX.test(suffix)) throw new Error("runtime account stage suffix invalid");
  return suffix;
}

function validateStagePath(stagePath) {
  if (typeof stagePath !== "string" || !isAbsolute(stagePath) || resolve(stagePath) !== stagePath) {
    throw new Error("runtime account stage path invalid");
  }
  const name = basename(stagePath);
  const suffix = name.startsWith(STAGE_PREFIX) ? name.slice(STAGE_PREFIX.length) : "";
  validateSuffix(suffix);
  if (dirname(stagePath) === "/") throw new Error("runtime account stage parent invalid");
  return { stagePath, suffix };
}

function validateUnit(unit, suffix) {
  const expected = `${UNIT_PREFIX}${suffix}`;
  if (unit !== expected) throw new Error("runtime account cleanup unit invalid");
  return expected;
}

function validateIdentityInteger(value, label, allowZero = false) {
  if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1)) throw new Error(`runtime account stage ${label} invalid`);
  return value;
}

function parseIdentityInteger(value, label, allowZero = false) {
  if (!/^(?:0|[1-9][0-9]*)$/.test(value)) throw new Error(`runtime account stage ${label} invalid`);
  return validateIdentityInteger(Number(value), label, allowZero);
}

function validateStage(stage) {
  if (!plain(stage) || Object.keys(stage).sort().join("|") !== "dev|ino|stagePath|unit") {
    throw new Error("runtime account stage invalid");
  }
  const validated = validateStagePath(stage.stagePath);
  return Object.freeze({
    stagePath: validated.stagePath,
    unit: validateUnit(stage.unit, validated.suffix),
    dev: validateIdentityInteger(stage.dev, "device", true),
    ino: validateIdentityInteger(stage.ino, "inode"),
  });
}

function stageAssertion(stage) {
  return [
    "uid=$(id -u)",
    `[ "$(stat -c '%f:%u:%d:%i' -- "$stage")" = "41c0:$uid:${stage.dev}:${stage.ino}" ] || exit 1`,
  ];
}

export function loadRuntimeAccountSource({ dispatchPath, piAuthPath, model, expectedIdentity }, deps = {}) {
  if (typeof model !== "string" || !model || control(model)) throw new Error("runtime account model invalid");
  if (typeof expectedIdentity !== "string" || !expectedIdentity || control(expectedIdentity)) {
    throw new Error("runtime account expected identity invalid");
  }
  const fstat = deps.fstatSync ?? fstatSync;
  const geteuid = deps.geteuid ?? process.geteuid;
  if (typeof fstat !== "function" || typeof geteuid !== "function") throw new Error("runtime account ownership check unavailable");
  const dispatchFile = readPrivateRegular(dispatchPath, "runtime account dispatch template", fstat);
  const authFile = readPrivateRegular(piAuthPath, "runtime account PI auth", fstat);
  const owner = geteuid();
  if (!Number.isSafeInteger(owner) || owner < 0) throw new Error("runtime account effective owner invalid");
  if (dispatchFile.stat.uid !== owner) throw new Error("runtime account dispatch template owner mismatch");
  if (authFile.stat.uid !== owner) throw new Error("runtime account PI auth owner mismatch");
  const dispatch = validateRunDispatchV1(parseJson(dispatchFile.bytes, "runtime account dispatch template"));
  const auth = authFile.bytes;
  if (dispatch.model !== model) throw new Error("runtime account dispatch model mismatch");
  const accountMatch = /^codex:([a-z0-9][a-z0-9._-]{0,127})$/.exec(dispatch.accountRef);
  if (!accountMatch || !SLUG.test(accountMatch[1])) throw new Error("runtime account dispatch account invalid");
  const provider = model.split("/", 1)[0];
  if (!SLUG.test(provider)) throw new Error("runtime account provider invalid");
  const parsedAuth = parseJson(auth, "runtime account PI auth");
  const providerAuth = parsedAuth?.[provider];
  if (!plain(providerAuth)) throw new Error("runtime account provider auth malformed");
  const observedIdentity = providerAuth.accountId;
  if (typeof observedIdentity !== "string" || !observedIdentity || control(observedIdentity)) {
    throw new Error("runtime account provider identity invalid");
  }
  if (observedIdentity !== expectedIdentity) throw new Error("runtime account provider identity mismatch");
  return Object.freeze({
    auth: Buffer.from(auth),
    dispatch,
    provider,
    slug: accountMatch[1],
    identityFingerprint: createHash("sha256").update(expectedIdentity).digest("hex"),
  });
}

export function materializeRuntimeAccountStage({ source, stagePath }) {
  const validated = validateStagePath(stagePath);
  if (!source || !Buffer.isBuffer(source.auth) || !plain(source.dispatch)
      || !SLUG.test(source.slug) || !SLUG.test(source.provider) || !SHA256.test(source.identityFingerprint)) {
    throw new Error("runtime account source invalid");
  }
  const accountRegistry = join(validated.stagePath, "accounts.json");
  const stagedDispatch = validateRunDispatchV1({ ...source.dispatch, accountRegistry });
  return Object.freeze({
    registryJson: `${canonicalJson({ schemaVersion: 1, accounts: [{ slug: source.slug }] })}\n`,
    dispatchJson: `${canonicalJson(stagedDispatch)}\n`,
  });
}

export function buildRuntimeAccountCreateCommand({ suffix }) {
  const safeSuffix = validateSuffix(suffix);
  const stageName = `${STAGE_PREFIX}${safeSuffix}`;
  const unit = `${UNIT_PREFIX}${safeSuffix}`;
  return [
    "set -eu",
    "uid=$(id -u)",
    'runtime=${XDG_RUNTIME_DIR:-/run/user/$uid}',
    'case "$runtime" in /*) ;; *) exit 1 ;; esac',
    '[ -d "$runtime" ] && [ ! -L "$runtime" ] || exit 1',
    '[ "$(stat -c %u -- "$runtime")" = "$uid" ] || exit 1',
    'mode=$(stat -c %a -- "$runtime")',
    'case "$mode" in 700) ;; *) exit 1 ;; esac',
    '[ "$(findmnt -n -o FSTYPE --target "$runtime")" = tmpfs ] || exit 1',
    `stage="$runtime/${stageName}"`,
    `unit=${shellQuote(unit)}`,
    "umask 077",
    'mkdir -m 700 -- "$stage"',
    'stageDev=$(stat -c %d -- "$stage")',
    'stageIno=$(stat -c %i -- "$stage")',
    'case "$stageDev:$stageIno" in *[!0-9:]*|:*|*:) rmdir -- "$stage"; exit 1 ;; esac',
    `if ! systemd-run --user --unit="$unit" --on-active=${TTL_SECONDS}s --collect -- /bin/rm -rf --one-file-system -- "$stage" >/dev/null 2>&1; then`,
    '  rmdir -- "$stage"',
    "  exit 1",
    "fi",
    'printf "stagePath=%s\\nunit=%s\\nstageDev=%s\\nstageIno=%s\\n" "$stage" "$unit" "$stageDev" "$stageIno"',
  ].join("\n");
}

export function parseRuntimeAccountCreateResult(output, { suffix }) {
  const safeSuffix = validateSuffix(suffix);
  const text = Buffer.isBuffer(output) ? output.toString("utf8") : String(output ?? "");
  const match = /^stagePath=(\/[^\n]+)\nunit=([^\n]+)\nstageDev=([^\n]+)\nstageIno=([^\n]+)\n$/.exec(text);
  if (!match) throw new Error("runtime account create result invalid");
  const validated = validateStagePath(match[1]);
  if (validated.suffix !== safeSuffix) throw new Error("runtime account create suffix mismatch");
  const unit = validateUnit(match[2], safeSuffix);
  const dev = parseIdentityInteger(match[3], "device", true);
  const ino = parseIdentityInteger(match[4], "inode");
  return Object.freeze({ stagePath: validated.stagePath, unit, dev, ino });
}

const VERIFY_AUTH = String.raw`
const fs = require("node:fs");
const crypto = require("node:crypto");
const [path, provider, expectedHex] = process.argv.slice(1);
let parsed;
try { parsed = JSON.parse(fs.readFileSync(path, "utf8")); } catch { process.exit(1); }
const entry = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed[provider] : null;
if (!entry || typeof entry !== "object" || Array.isArray(entry)) process.exit(1);
const identity = entry.accountId;
if (typeof identity !== "string" || !identity || Array.from(identity).some((c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127)) process.exit(1);
const actual = crypto.createHash("sha256").update(identity).digest();
const expected = Buffer.from(expectedHex, "hex");
if (expected.length !== actual.length || !crypto.timingSafeEqual(actual, expected)) process.exit(1);
`;

export function buildRuntimeAccountInstallCommand({ source, materialized, stage }) {
  const validated = validateStage(stage);
  if (!source || !Buffer.isBuffer(source.auth) || !SLUG.test(source.slug)
      || !SLUG.test(source.provider) || !SHA256.test(source.identityFingerprint)) {
    throw new Error("runtime account source invalid");
  }
  if (!materialized || typeof materialized.registryJson !== "string" || typeof materialized.dispatchJson !== "string") {
    throw new Error("runtime account materialization invalid");
  }
  const registry = Buffer.from(materialized.registryJson).toString("base64");
  const dispatch = Buffer.from(materialized.dispatchJson).toString("base64");
  const stageQuoted = shellQuote(validated.stagePath);
  const slug = shellQuote(source.slug);
  const provider = shellQuote(source.provider);
  const fingerprint = shellQuote(source.identityFingerprint);
  const cleanup = '/bin/rm -rf --one-file-system -- "$stage/accounts"; /bin/rm -f -- "$authTmp" "$registryTmp" "$dispatchTmp" "$stage/accounts.json" "$stage/dispatch.json"';
  return [
    "set -eu",
    `stage=${stageQuoted}`,
    ...stageAssertion(validated),
    '[ -z "$(find "$stage" -mindepth 1 -maxdepth 1 -print -quit)" ] || exit 1',
    "umask 077",
    'authTmp="$stage/.auth.tmp"',
    'registryTmp="$stage/.accounts.tmp"',
    'dispatchTmp="$stage/.dispatch.tmp"',
    "cleanup() { " + cleanup + "; }",
    "trap cleanup EXIT HUP INT TERM",
    'set -C; : > "$authTmp"; set +C',
    'cat > "$authTmp"',
    '[ -s "$authTmp" ] || exit 1',
    `node -e ${shellQuote(VERIFY_AUTH)} "$authTmp" ${provider} ${fingerprint}`,
    `printf %s ${shellQuote(registry)} | base64 -d > "$registryTmp"`,
    `printf %s ${shellQuote(dispatch)} | base64 -d > "$dispatchTmp"`,
    `mkdir -m 700 -- "$stage/accounts" "$stage/accounts/${source.slug}" "$stage/accounts/${source.slug}/PI_HOME"`,
    'chmod 600 -- "$authTmp" "$registryTmp" "$dispatchTmp"',
    `mv -- "$authTmp" "$stage/accounts/${source.slug}/PI_HOME/auth.json"`,
    'mv -- "$registryTmp" "$stage/accounts.json"',
    'mv -- "$dispatchTmp" "$stage/dispatch.json"',
    "trap - EXIT HUP INT TERM",
  ].join("\n");
}

export function buildRuntimeAccountCheckCommand({ stage }) {
  const validated = validateStage(stage);
  return [
    "set -eu",
    `stage=${shellQuote(validated.stagePath)}`,
    ...stageAssertion(validated),
  ].join("\n");
}

export function buildRuntimeAccountCleanupCommands({ stage }) {
  const validated = validateStage(stage);
  const stageQuoted = shellQuote(validated.stagePath);
  const timer = shellQuote(`${validated.unit}.timer`);
  const service = shellQuote(`${validated.unit}.service`);
  return Object.freeze({
    deleteCommand: [
      "set -eu",
      `stage=${stageQuoted}`,
      ...stageAssertion(validated),
      '  /bin/rm -rf --one-file-system -- "$stage"',
      '[ ! -e "$stage" ] && [ ! -L "$stage" ]',
    ].join("\n"),
    disarmCommand: [
      "set -eu",
      `systemctl --user stop ${timer} ${service}`,
      `systemctl --user reset-failed ${timer} ${service}`,
    ].join("\n"),
  });
}
