import { createHash, createPublicKey, timingSafeEqual, verify as verifySignature } from "node:crypto";
import { closeSync, constants as fsConstants, fstatSync, lstatSync, openSync, readFileSync } from "node:fs";
import { isAbsolute } from "node:path";

export const MANAGED_PROVISIONER_SIGNATURE_SCHEMA = "nodedc.external-data-plane.managed-provisioner-request/v1";

export const MANAGED_PROVISIONER_HEADERS = Object.freeze({
  serviceId: "x-nodedc-engine-service-id",
  keyId: "x-nodedc-engine-key-id",
  audience: "x-nodedc-request-audience",
  timestamp: "x-nodedc-request-timestamp",
  nonce: "x-nodedc-request-nonce",
  bodySha256: "x-nodedc-content-sha256",
  signature: "x-nodedc-request-signature",
});

const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
const BASE64URL = /^[A-Za-z0-9_-]+$/;
const IDENTITY = /^[a-z][a-z0-9._:-]{2,127}$/i;
const AUDIENCE = /^[a-z][a-z0-9._:/-]{2,255}$/i;

export function loadManagedProvisionerPublicKeyFile(pathValue) {
  const path = String(pathValue ?? "").trim();
  if (!path) throw configError("external_data_plane_managed_provisioner_public_key_file_required");
  if (!isAbsolute(path)) throw configError("external_data_plane_managed_provisioner_public_key_file_must_be_absolute");

  let stat;
  try {
    stat = lstatSync(path);
  } catch {
    throw configError("external_data_plane_managed_provisioner_public_key_file_unreadable");
  }
  if (!stat.isFile() || stat.isSymbolicLink()) {
    throw configError("external_data_plane_managed_provisioner_public_key_file_must_be_regular");
  }
  if ((stat.mode & 0o022) !== 0) {
    throw configError("external_data_plane_managed_provisioner_public_key_file_permissions_invalid");
  }
  if (stat.size < 1 || stat.size > 8192) {
    throw configError("external_data_plane_managed_provisioner_public_key_file_size_invalid");
  }

  let descriptor;
  try {
    descriptor = openSync(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
  } catch {
    throw configError("external_data_plane_managed_provisioner_public_key_file_unreadable");
  }
  let pem;
  try {
    const openedStat = fstatSync(descriptor);
    if (!openedStat.isFile()) {
      throw configError("external_data_plane_managed_provisioner_public_key_file_must_be_regular");
    }
    if ((openedStat.mode & 0o022) !== 0) {
      throw configError("external_data_plane_managed_provisioner_public_key_file_permissions_invalid");
    }
    if (openedStat.size < 1 || openedStat.size > 8192) {
      throw configError("external_data_plane_managed_provisioner_public_key_file_size_invalid");
    }
    pem = readFileSync(descriptor, "utf8");
  } catch (error) {
    if (String(error?.code || "").startsWith("external_data_plane_managed_provisioner_")) throw error;
    throw configError("external_data_plane_managed_provisioner_public_key_file_unreadable");
  } finally {
    closeSync(descriptor);
  }
  const normalizedPem = pem.replace(/\r\n/g, "\n").trim();
  if (!/^-----BEGIN PUBLIC KEY-----\n[A-Za-z0-9+/=\n]+\n-----END PUBLIC KEY-----$/.test(normalizedPem)) {
    throw configError("external_data_plane_managed_provisioner_public_key_file_format_invalid");
  }

  let publicKey;
  try {
    publicKey = createPublicKey(normalizedPem);
  } catch {
    throw configError("external_data_plane_managed_provisioner_public_key_file_format_invalid");
  }
  if (publicKey.type !== "public" || publicKey.asymmetricKeyType !== "ed25519") {
    throw configError("external_data_plane_managed_provisioner_public_key_algorithm_invalid");
  }
  return publicKey;
}

export function validateManagedProvisionerIdentity(value, name) {
  const normalized = String(value ?? "").trim();
  if (!IDENTITY.test(normalized)) throw configError(`external_data_plane_managed_provisioner_${name}_invalid`);
  return normalized;
}

export function validateManagedProvisionerAudience(value) {
  const normalized = String(value ?? "").trim();
  if (!AUDIENCE.test(normalized)) throw configError("external_data_plane_managed_provisioner_audience_invalid");
  return normalized;
}

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

export function sha256RawBody(rawBody) {
  if (!Buffer.isBuffer(rawBody)) throw new TypeError("raw_body_buffer_required");
  return createHash("sha256").update(rawBody).digest("hex");
}

export class ManagedProvisionerReplayCache {
  constructor({ maxEntries }) {
    if (!Number.isInteger(maxEntries) || maxEntries < 1) throw new TypeError("replay_cache_max_entries_invalid");
    this.maxEntries = maxEntries;
    this.entries = new Map();
  }

  consume(key, expiresAt, now = Date.now()) {
    this.prune(now);
    const existing = this.entries.get(key);
    if (existing !== undefined && existing > now) {
      throw authError(409, "managed_provisioner_request_replayed");
    }
    if (this.entries.size >= this.maxEntries) {
      throw authError(503, "managed_provisioner_replay_cache_exhausted");
    }
    this.entries.set(key, expiresAt);
  }

  prune(now = Date.now()) {
    for (const [key, expiresAt] of this.entries) {
      if (expiresAt <= now) this.entries.delete(key);
    }
  }
}

export function createManagedProvisionerRequestVerifier({
  publicKey,
  serviceId,
  keyId,
  audience,
  maxSkewMs,
  replayCache,
  now = Date.now,
}) {
  if (publicKey?.type !== "public" || publicKey?.asymmetricKeyType !== "ed25519") {
    throw configError("external_data_plane_managed_provisioner_public_key_algorithm_invalid");
  }
  if (!IDENTITY.test(serviceId)) throw configError("external_data_plane_managed_provisioner_service_id_invalid");
  if (!IDENTITY.test(keyId)) throw configError("external_data_plane_managed_provisioner_key_id_invalid");
  if (!AUDIENCE.test(audience)) throw configError("external_data_plane_managed_provisioner_audience_invalid");
  if (!Number.isInteger(maxSkewMs) || maxSkewMs < 1) {
    throw configError("external_data_plane_managed_provisioner_max_skew_invalid");
  }
  if (!(replayCache instanceof ManagedProvisionerReplayCache)) {
    throw configError("external_data_plane_managed_provisioner_replay_cache_invalid");
  }

  return function verifyManagedProvisionerRequest(req) {
    const requestServiceId = exactHeader(req, MANAGED_PROVISIONER_HEADERS.serviceId);
    const requestKeyId = exactHeader(req, MANAGED_PROVISIONER_HEADERS.keyId);
    const requestAudience = exactHeader(req, MANAGED_PROVISIONER_HEADERS.audience);
    const timestamp = exactHeader(req, MANAGED_PROVISIONER_HEADERS.timestamp);
    const nonce = exactHeader(req, MANAGED_PROVISIONER_HEADERS.nonce);
    const declaredBodySha256 = exactHeader(req, MANAGED_PROVISIONER_HEADERS.bodySha256);
    const encodedSignature = exactHeader(req, MANAGED_PROVISIONER_HEADERS.signature);
    if (hasHeader(req, "authorization")) {
      throw authError(401, "managed_provisioner_authorization_header_forbidden");
    }

    if (!safeEqualText(requestServiceId, serviceId)
      || !safeEqualText(requestKeyId, keyId)
      || !safeEqualText(requestAudience, audience)) {
      throw authError(401, "managed_provisioner_identity_mismatch");
    }

    const timestampMs = parseTimestamp(timestamp);
    const verificationTime = Number(now());
    if (!Number.isFinite(verificationTime) || Math.abs(verificationTime - timestampMs) >= maxSkewMs) {
      throw authError(401, "managed_provisioner_timestamp_outside_window");
    }
    const nonceBytes = decodeCanonicalBase64Url(nonce, null, "managed_provisioner_nonce_invalid");
    if (nonceBytes.length < 16 || nonceBytes.length > 96) throw authError(401, "managed_provisioner_nonce_invalid");
    if (!/^[a-f0-9]{64}$/.test(declaredBodySha256)) {
      throw authError(401, "managed_provisioner_body_hash_invalid");
    }

    const method = exactMethod(req);
    const path = exactPath(req);
    const rawBody = exactRawBody(req);
    const actualBodySha256 = sha256RawBody(rawBody);
    if (!safeEqualText(declaredBodySha256, actualBodySha256)) {
      throw authError(401, "managed_provisioner_body_hash_mismatch");
    }

    const signature = decodeCanonicalBase64Url(encodedSignature, 64, "managed_provisioner_signature_invalid");
    const payload = managedProvisionerSigningPayload({
      audience: requestAudience,
      serviceId: requestServiceId,
      keyId: requestKeyId,
      method,
      path,
      timestamp,
      nonce,
      bodySha256: declaredBodySha256,
    });
    if (!verifySignature(null, Buffer.from(payload, "utf8"), publicKey, signature)) {
      throw authError(401, "managed_provisioner_signature_invalid");
    }

    const replayKey = `${requestServiceId}\0${requestKeyId}\0${nonce}`;
    replayCache.consume(replayKey, timestampMs + maxSkewMs, verificationTime);
    return Object.freeze({ serviceId: requestServiceId, keyId: requestKeyId });
  };
}

function exactHeader(req, name) {
  const rawHeaders = req?.rawHeaders;
  if (Array.isArray(rawHeaders)) {
    const values = [];
    for (let index = 0; index < rawHeaders.length; index += 2) {
      if (String(rawHeaders[index]).toLowerCase() === name) values.push(rawHeaders[index + 1]);
    }
    if (values.length !== 1 || typeof values[0] !== "string" || !values[0]) {
      throw authError(401, "managed_provisioner_header_invalid");
    }
    return values[0];
  }

  const value = req?.headers?.[name];
  if (typeof value !== "string" || !value) throw authError(401, "managed_provisioner_header_invalid");
  return value;
}

function hasHeader(req, name) {
  if (Array.isArray(req?.rawHeaders)) {
    for (let index = 0; index < req.rawHeaders.length; index += 2) {
      if (String(req.rawHeaders[index]).toLowerCase() === name) return true;
    }
    return false;
  }
  return Object.hasOwn(req?.headers || {}, name);
}

function exactMethod(req) {
  const method = String(req?.method ?? "");
  if (!/^[A-Z]{3,10}$/.test(method)) throw authError(401, "managed_provisioner_method_invalid");
  return method;
}

function exactPath(req) {
  const path = String(req?.originalUrl ?? "");
  if (!path.startsWith("/") || path.length > 2048 || /[\u0000-\u0020#]/.test(path)) {
    throw authError(401, "managed_provisioner_path_invalid");
  }
  return path;
}

function exactRawBody(req) {
  if (req?.rawBodyCaptured === true && Buffer.isBuffer(req.rawBody)) return req.rawBody;
  const contentLength = req?.headers?.["content-length"];
  const transferEncoding = req?.headers?.["transfer-encoding"];
  if ((contentLength === undefined || contentLength === "0") && transferEncoding === undefined) {
    return Buffer.alloc(0);
  }
  throw authError(401, "managed_provisioner_raw_body_unavailable");
}

function parseTimestamp(value) {
  if (!ISO_TIMESTAMP.test(value)) throw authError(401, "managed_provisioner_timestamp_invalid");
  const timestampMs = Date.parse(value);
  if (!Number.isFinite(timestampMs) || new Date(timestampMs).toISOString() !== value) {
    throw authError(401, "managed_provisioner_timestamp_invalid");
  }
  return timestampMs;
}

function decodeCanonicalBase64Url(value, expectedBytes, errorCode) {
  if (!BASE64URL.test(value)) throw authError(401, errorCode);
  let decoded;
  try {
    decoded = Buffer.from(value, "base64url");
  } catch {
    throw authError(401, errorCode);
  }
  if ((expectedBytes !== null && decoded.length !== expectedBytes) || decoded.toString("base64url") !== value) {
    throw authError(401, errorCode);
  }
  return decoded;
}

function safeEqualText(left, right) {
  const leftBuffer = Buffer.from(String(left), "utf8");
  const rightBuffer = Buffer.from(String(right), "utf8");
  return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
}

function authError(status, code) {
  return Object.assign(new Error(code), { status, code });
}

function configError(code) {
  return Object.assign(new Error(code), { code });
}
