import { constants as fsConstants } from "node:fs";
import { chmod, lstat, mkdir, open } from "node:fs/promises";
import { createHash, createPrivateKey, randomBytes, randomUUID, sign } from "node:crypto";
import { join, resolve } from "node:path";

const SIGNATURE_SCHEMA = "nodedc.external-data-plane.managed-provisioner-request/v1";
const TOKEN_PATTERN = /^ndc_edprb_[A-Za-z0-9_-]{32,512}$/;
const IDENTITY_PATTERN = /^[a-z][a-z0-9._:-]{2,127}$/i;
const AUDIENCE_PATTERN = /^[a-z][a-z0-9._:/-]{2,255}$/i;

function provisionerError(code, statusCode = 500) {
  return Object.assign(new Error(code), { statusCode });
}

function targetAddress(target) {
  const applicationId = String(target?.application?.id || target?.applicationId || "");
  const pageId = String(target?.page?.id || target?.pageId || "");
  const bindingId = String(target?.binding?.id || target?.bindingId || "");
  if (!/^[0-9a-f-]{36}$/i.test(applicationId) || !IDENTITY_PATTERN.test(pageId)
    || !IDENTITY_PATTERN.test(bindingId)) {
    throw provisionerError("foundry_reader_grant_target_invalid", 400);
  }
  return { applicationId, pageId, bindingId };
}

function targetIdentity(target) {
  const address = targetAddress(target);
  const dataProductId = String(target?.binding?.dataProductId || target?.dataProductId || "");
  if (!IDENTITY_PATTERN.test(dataProductId)) throw provisionerError("foundry_reader_grant_target_invalid", 400);
  const { applicationId, pageId, bindingId } = address;
  return { applicationId, pageId, bindingId, dataProductId };
}

function targetDigest(target) {
  const identity = targetAddress(target);
  return createHash("sha256")
    .update(`${identity.applicationId}/${identity.pageId}/${identity.bindingId}`, "utf8")
    .digest("hex");
}

function grantGeneration(value) {
  const generation = Number(value ?? 1);
  if (!Number.isSafeInteger(generation) || generation < 1 || generation > 2_147_483_647) {
    throw provisionerError("foundry_reader_grant_generation_invalid", 400);
  }
  return generation;
}

function readerTokenPath(tokenRoot, digest, generation) {
  // Generation one keeps the deployed path for a zero-copy migration. Every
  // successor gets a separate immutable capability file so a new snapshot can
  // be accepted before the predecessor is revoked.
  return join(resolve(tokenRoot), generation === 1 ? digest : `${digest}.g${generation}`);
}

function signingPayload({ audience, serviceId, keyId, method, path, timestamp, nonce, bodySha256 }) {
  return JSON.stringify({
    schemaVersion: SIGNATURE_SCHEMA,
    audience,
    serviceId,
    keyId,
    method,
    path,
    timestamp,
    nonce,
    bodySha256,
  });
}

export function createFoundryReaderGrantProvisioner({
  dataPlaneUrl,
  privateKeyFile,
  grantsDir,
  serviceId = "nodedc-module-foundry",
  keyId = "foundry-edp-managed-provisioner-v1",
  audience = "nodedc-external-data-plane.managed-provisioning.v1",
  fetchImpl = fetch,
  now = () => new Date(),
  randomBytesImpl = randomBytes,
  randomUUIDImpl = randomUUID,
  production = process.env.NODE_ENV === "production",
}) {
  const baseUrl = String(dataPlaneUrl || "").trim().replace(/\/$/, "");
  const keyPath = String(privateKeyFile || "").trim();
  const tokenRoot = String(grantsDir || "").trim();
  const configured = Boolean(baseUrl && keyPath && tokenRoot && IDENTITY_PATTERN.test(serviceId)
    && IDENTITY_PATTERN.test(keyId) && AUDIENCE_PATTERN.test(audience));
  let privateKeyPromise = null;

  async function loadPrivateKey() {
    if (!configured) throw provisionerError("foundry_reader_grant_provisioner_not_configured", 503);
    if (!privateKeyPromise) privateKeyPromise = readPrivateKeySecurely(keyPath, { production });
    return privateKeyPromise;
  }

  async function signedRequest(method, path, value) {
    const privateKey = await loadPrivateKey();
    const body = JSON.stringify(value);
    const bodySha256 = createHash("sha256").update(body, "utf8").digest("hex");
    const timestamp = now().toISOString();
    const nonce = randomUUIDImpl();
    const signature = sign(null, Buffer.from(signingPayload({
      audience,
      serviceId,
      keyId,
      method,
      path,
      timestamp,
      nonce,
      bodySha256,
    }), "utf8"), privateKey).toString("base64url");
    let response;
    try {
      response = await fetchImpl(new URL(path, `${baseUrl}/`), {
        method,
        headers: {
          "content-type": "application/json",
          accept: "application/json",
          "x-nodedc-engine-service-id": serviceId,
          "x-nodedc-engine-key-id": keyId,
          "x-nodedc-request-audience": audience,
          "x-nodedc-request-timestamp": timestamp,
          "x-nodedc-request-nonce": nonce,
          "x-nodedc-content-sha256": bodySha256,
          "x-nodedc-request-signature": signature,
        },
        body,
        signal: AbortSignal.timeout(10_000),
      });
    } catch {
      throw provisionerError("foundry_reader_grant_provisioner_unavailable", 503);
    }
    const payload = await response.json().catch(() => null);
    if (!response.ok) {
      const code = String(payload?.error || payload?.code || "foundry_reader_grant_provisioner_rejected");
      throw provisionerError(/^[a-z][a-z0-9_]{2,120}$/.test(code) ? code : "foundry_reader_grant_provisioner_rejected", response.status);
    }
    return payload;
  }

  async function plan(target) {
    const identity = targetIdentity(target);
    const payload = await signedRequest("POST", "/internal/data-plane/v1/consumer-reader-bindings/plan", {
      allowedDataProductIds: [identity.dataProductId],
    });
    const product = Array.isArray(payload?.dataProducts)
      ? payload.dataProducts.find((candidate) => candidate?.id === identity.dataProductId)
      : null;
    if (!product || payload?.sourceScope !== "resolved-server-side") {
      throw provisionerError("foundry_reader_grant_plan_response_invalid", 502);
    }
    return { product, sourceScope: "resolved-server-side" };
  }

  async function ensure(target, options = {}) {
    const identity = targetIdentity(target);
    const digest = targetDigest(identity);
    const generation = grantGeneration(options.generation);
    const token = await ensureReaderToken(readerTokenPath(tokenRoot, digest, generation), {
      production,
      randomBytesImpl,
    });
    const bindingKey = `fndrc-${digest}`;
    const payload = await signedRequest(
      "PUT",
      `/internal/data-plane/v1/consumer-reader-bindings/by-key/${encodeURIComponent(bindingKey)}`,
      {
        allowedDataProductIds: [identity.dataProductId],
        expiresAt: null,
        generation,
        capabilityDigest: createHash("sha256").update(token, "utf8").digest("hex"),
      },
    );
    const binding = payload?.readerBinding;
    if (binding?.bindingKey !== bindingKey || binding?.generation !== generation || binding?.active !== true
      || binding?.expiresAt !== null || binding?.sourceScope !== "resolved-server-side"
      || !Array.isArray(binding?.allowedDataProductIds)
      || !binding.allowedDataProductIds.includes(identity.dataProductId)) {
      throw provisionerError("foundry_reader_grant_ensure_response_invalid", 502);
    }
    return { ensured: true, idempotent: payload?.idempotent === true, generation, sourceScope: "resolved-server-side" };
  }

  async function revoke(target, options = {}) {
    const digest = targetDigest(target);
    const generation = grantGeneration(options.generation);
    const bindingKey = `fndrc-${digest}`;
    const payload = await signedRequest(
      "POST",
      `/internal/data-plane/v1/consumer-reader-bindings/by-key/${encodeURIComponent(bindingKey)}/generations/${generation}/revoke`,
      {},
    );
    const binding = payload?.readerBinding;
    if (binding?.bindingKey !== bindingKey || binding?.generation !== generation || binding?.active !== false
      || binding?.sourceScope !== "resolved-server-side") {
      throw provisionerError("foundry_reader_grant_revoke_response_invalid", 502);
    }
    return { revoked: true, idempotent: payload?.idempotent === true, generation, sourceScope: "resolved-server-side" };
  }

  async function readToken(target, options = {}) {
    if (!tokenRoot) return null;
    const generation = grantGeneration(options.generation);
    return readReaderToken(readerTokenPath(tokenRoot, targetDigest(target), generation), { production, missing: null });
  }

  return { configured, plan, ensure, revoke, readToken };
}

async function readPrivateKeySecurely(path, { production }) {
  let handle;
  try {
    handle = await open(path, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW || 0));
  } catch {
    throw provisionerError("foundry_reader_grant_private_key_unreadable", 503);
  }
  try {
    const metadata = await handle.stat();
    if (!metadata.isFile() || metadata.size < 80 || metadata.size > 8192 || (metadata.mode & 0o022) !== 0
      || (production && metadata.uid !== 0)) {
      throw provisionerError("foundry_reader_grant_private_key_invalid", 503);
    }
    const pem = await handle.readFile("utf8");
    const privateKey = createPrivateKey(pem);
    if (privateKey.type !== "private" || privateKey.asymmetricKeyType !== "ed25519") {
      throw provisionerError("foundry_reader_grant_private_key_invalid", 503);
    }
    return privateKey;
  } catch (error) {
    if (String(error?.message || "").startsWith("foundry_reader_grant_")) throw error;
    throw provisionerError("foundry_reader_grant_private_key_invalid", 503);
  } finally {
    await handle.close();
  }
}

async function ensureReaderToken(path, { production, randomBytesImpl }) {
  const existing = await readReaderToken(path, { production, missing: null });
  if (existing) return existing;
  const directory = resolve(path, "..");
  await mkdir(directory, { recursive: true, mode: 0o700 });
  await chmod(directory, 0o700);
  const directoryMetadata = await lstat(directory);
  if (!directoryMetadata.isDirectory() || directoryMetadata.isSymbolicLink()
    || (directoryMetadata.mode & 0o077) !== 0 || (production && directoryMetadata.uid !== 0)) {
    throw provisionerError("foundry_reader_grant_store_invalid", 503);
  }
  const token = `ndc_edprb_${randomBytesImpl(32).toString("base64url")}`;
  let handle;
  try {
    handle = await open(
      path,
      fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | (fsConstants.O_NOFOLLOW || 0),
      0o400,
    );
    await handle.writeFile(`${token}\n`, "utf8");
    await handle.sync();
    await handle.chmod(0o400);
    return token;
  } catch (error) {
    if (error?.code === "EEXIST") return readConcurrentlyCreatedReaderToken(path, { production });
    throw provisionerError("foundry_reader_grant_store_unavailable", 503);
  } finally {
    await handle?.close();
  }
}

async function readConcurrentlyCreatedReaderToken(path, { production }) {
  for (let attempt = 0; attempt < 10; attempt += 1) {
    try {
      const token = await readReaderToken(path, { production, missing: null });
      if (token) return token;
    } catch (error) {
      if (error?.message !== "foundry_reader_grant_invalid") throw error;
    }
    await new Promise((resolveDelay) => setTimeout(resolveDelay, 10));
  }
  throw provisionerError("foundry_reader_grant_store_unavailable", 503);
}

async function readReaderToken(path, { production, missing }) {
  let handle;
  try {
    handle = await open(path, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW || 0));
  } catch (error) {
    if (error?.code === "ENOENT") return missing;
    throw provisionerError("foundry_reader_grant_unavailable", 503);
  }
  try {
    const metadata = await handle.stat();
    if (!metadata.isFile() || metadata.size < 2 || metadata.size > 1024 || (metadata.mode & 0o222) !== 0
      || (production && metadata.uid !== 0)) {
      throw provisionerError("foundry_reader_grant_invalid", 503);
    }
    const token = (await handle.readFile("utf8")).trim();
    if (!TOKEN_PATTERN.test(token)) throw provisionerError("foundry_reader_grant_invalid", 503);
    return token;
  } finally {
    await handle.close();
  }
}
