import {
  createPrivateKey,
  createPublicKey,
  sign as signBytes,
  verify as verifyBytes
} from "node:crypto";
import {
  RUNRAIL_PLAN_CATALOG_REVISION,
  runrailPlanForCode
} from "../../shared/runrail-plan-catalog.mjs";

// Schema v1 remains available only for installed-client compatibility.
export const RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION = 1;
export const RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V2 = 2;
export const RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V3 = 3;
export const RUNRAIL_ENTITLEMENT_LEASE_CURRENT_SCHEMA_VERSION =
  RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V3;
export const RUNRAIL_ENTITLEMENT_LEASE_AUTHORITY = "subscription-entitlement-only";
export const RUNRAIL_ENTITLEMENT_LEASE_MAX_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
export const RUNRAIL_ENTITLEMENT_LEASE_V2_DURATION_MILLISECONDS = 24 * 60 * 60 * 1000;
export const RUNRAIL_ENTITLEMENT_LEASE_V2_RENEW_AFTER_MILLISECONDS = 12 * 60 * 60 * 1000;
export const RUNRAIL_ENTITLEMENT_LEASE_V2_OFFLINE_GRACE_DURATION_MILLISECONDS =
  48 * 60 * 60 * 1000;
export const RUNRAIL_ENTITLEMENT_LEASE_V2_GRACE_UNTIL_MILLISECONDS =
  RUNRAIL_ENTITLEMENT_LEASE_V2_DURATION_MILLISECONDS +
  RUNRAIL_ENTITLEMENT_LEASE_V2_OFFLINE_GRACE_DURATION_MILLISECONDS;

const V1_PAYLOAD_KEYS = Object.freeze([
  "schemaVersion",
  "catalogRevision",
  "accountId",
  "workspaceId",
  "deviceId",
  "planCode",
  "features",
  "issuedAt",
  "expiresAt",
  "keyId",
  "authority"
]);
const V2_PAYLOAD_KEYS = Object.freeze([
  "schemaVersion",
  "catalogRevision",
  "accountId",
  "workspaceId",
  "deviceId",
  "devicePublicKeySha256",
  "leaseSequence",
  "planCode",
  "features",
  "issuedAt",
  "renewAfter",
  "expiresAt",
  "graceUntil",
  "keyId",
  "authority",
  "executionAuthorityGranted"
]);
const V3_PAYLOAD_KEYS = Object.freeze([
  "schemaVersion",
  "catalogRevision",
  "accountId",
  "workspaceId",
  "deviceId",
  "devicePublicKeySha256",
  "leaseSequence",
  "planCode",
  "features",
  "subscriptionStatus",
  "trialStartedAt",
  "trialEndsAt",
  "maxDevices",
  "featureSet",
  "issuedAt",
  "renewAfter",
  "expiresAt",
  "graceUntil",
  "keyId",
  "authority",
  "executionAuthorityGranted"
]);
const ENVELOPE_KEYS = Object.freeze(["payload", "signature"]);
const V1_ISSUE_KEYS = new Set([
  "accountId",
  "workspaceId",
  "deviceId",
  "planCode",
  "issuedAt",
  "expiresAt",
  "keyId",
  "privateKey",
  "contractedEnterprise"
]);
const V2_ISSUE_KEYS = new Set([
  ...V1_ISSUE_KEYS,
  "devicePublicKeySha256",
  "leaseSequence",
  "renewAfter",
  "graceUntil"
]);
const V3_ISSUE_KEYS = new Set([
  ...V2_ISSUE_KEYS,
  "subscriptionStatus",
  "trialStartedAt",
  "trialEndsAt",
  "maxDevices",
  "featureSet"
]);
const VERIFY_KEYS = new Set([
  "publicKeysByKeyId",
  "expectedDeviceId",
  "expectedDevicePublicKeySha256",
  "expectedLeaseSequence",
  "allowGrace",
  "now"
]);
const OPAQUE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{15,127}$/;
const KEY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;

function assertObject(value, label) {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    throw new TypeError(`${label} must be an object.`);
  }
}

function assertExactKeys(value, expectedKeys, label) {
  assertObject(value, label);
  const actual = Object.keys(value).sort();
  const expected = [...expectedKeys].sort();
  if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
    throw new TypeError(`${label} contains missing or unsupported fields.`);
  }
}

function assertAllowedKeys(value, allowedKeys, label) {
  assertObject(value, label);
  for (const key of Object.keys(value)) {
    if (!allowedKeys.has(key)) {
      throw new TypeError(`${label} contains an unsupported field: ${key}.`);
    }
  }
}

function assertOpaqueId(value, label) {
  if (typeof value !== "string" || !OPAQUE_ID_PATTERN.test(value)) {
    throw new TypeError(`${label} must be an opaque 16-128 character identifier.`);
  }
}

function assertKeyId(value) {
  if (typeof value !== "string" || !KEY_ID_PATTERN.test(value)) {
    throw new TypeError("keyId must be a 1-64 character identifier.");
  }
}

function assertSha256(value, label) {
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
    throw new TypeError(`${label} must be a lowercase SHA-256 digest.`);
  }
}

function assertLeaseSequence(value, label = "leaseSequence") {
  if (!Number.isSafeInteger(value) || value < 1) {
    throw new TypeError(`${label} must be a positive safe integer.`);
  }
}

function timestampFromInput(value, label) {
  const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
  if (!Number.isFinite(date.getTime())) {
    throw new TypeError(`${label} must be a valid timestamp.`);
  }
  return date.toISOString();
}

function timestampMilliseconds(value, label) {
  if (typeof value !== "string") {
    throw new TypeError(`${label} must be a canonical UTC timestamp.`);
  }
  const milliseconds = Date.parse(value);
  if (!Number.isFinite(milliseconds) || new Date(milliseconds).toISOString() !== value) {
    throw new TypeError(`${label} must be a canonical UTC timestamp.`);
  }
  return milliseconds;
}

function assertCanonicalFeatures(features, plan) {
  if (!Array.isArray(features) || features.length !== plan.features.length) {
    throw new TypeError("Lease features do not match the canonical plan.");
  }
  for (let index = 0; index < plan.features.length; index += 1) {
    if (features[index] !== plan.features[index]) {
      throw new TypeError("Lease features do not match the canonical plan.");
    }
  }
}

function validatePlan(planCode) {
  if (typeof planCode !== "string") {
    throw new TypeError("planCode must be a canonical catalog code.");
  }
  const plan = runrailPlanForCode(planCode);
  if (!plan || plan.code !== planCode) {
    throw new TypeError("Entitlement lease plan is not in the canonical catalog.");
  }
  return plan;
}

function validateV1Payload(payload) {
  assertExactKeys(payload, V1_PAYLOAD_KEYS, "Entitlement lease payload");
  const issuedAtMilliseconds = timestampMilliseconds(payload.issuedAt, "issuedAt");
  const expiresAtMilliseconds = timestampMilliseconds(payload.expiresAt, "expiresAt");
  const duration = expiresAtMilliseconds - issuedAtMilliseconds;
  if (duration <= 0) throw new TypeError("Lease expiry must be after issuance.");
  if (duration > RUNRAIL_ENTITLEMENT_LEASE_MAX_MILLISECONDS) {
    throw new TypeError("Lease duration cannot exceed seven days.");
  }
  return {
    issuedAtMilliseconds,
    expiresAtMilliseconds,
    graceUntilMilliseconds: expiresAtMilliseconds
  };
}

function validateV2Payload(payload) {
  assertExactKeys(payload, V2_PAYLOAD_KEYS, "Entitlement lease payload");
  assertSha256(payload.devicePublicKeySha256, "devicePublicKeySha256");
  assertLeaseSequence(payload.leaseSequence);
  if (payload.executionAuthorityGranted !== false) {
    throw new TypeError("Entitlement leases cannot grant execution authority.");
  }
  const issuedAtMilliseconds = timestampMilliseconds(payload.issuedAt, "issuedAt");
  const renewAfterMilliseconds = timestampMilliseconds(payload.renewAfter, "renewAfter");
  const expiresAtMilliseconds = timestampMilliseconds(payload.expiresAt, "expiresAt");
  const graceUntilMilliseconds = timestampMilliseconds(payload.graceUntil, "graceUntil");
  if (
    renewAfterMilliseconds - issuedAtMilliseconds !==
      RUNRAIL_ENTITLEMENT_LEASE_V2_RENEW_AFTER_MILLISECONDS ||
    expiresAtMilliseconds - issuedAtMilliseconds !==
      RUNRAIL_ENTITLEMENT_LEASE_V2_DURATION_MILLISECONDS ||
    graceUntilMilliseconds - issuedAtMilliseconds !==
      RUNRAIL_ENTITLEMENT_LEASE_V2_GRACE_UNTIL_MILLISECONDS
  ) {
    throw new TypeError("Lease v2 validity metadata is outside the fixed Runrail window.");
  }
  if (!(issuedAtMilliseconds < renewAfterMilliseconds && renewAfterMilliseconds < expiresAtMilliseconds)) {
    throw new TypeError("Lease v2 renewal time must fall inside its normal validity window.");
  }
  if (graceUntilMilliseconds <= expiresAtMilliseconds) {
    throw new TypeError("Lease v2 grace deadline must follow normal expiry.");
  }
  return { issuedAtMilliseconds, expiresAtMilliseconds, graceUntilMilliseconds };
}

function validateV3Payload(payload, plan) {
  assertExactKeys(payload, V3_PAYLOAD_KEYS, "Entitlement lease payload");
  assertSha256(payload.devicePublicKeySha256, "devicePublicKeySha256");
  assertLeaseSequence(payload.leaseSequence);
  if (payload.executionAuthorityGranted !== false) {
    throw new TypeError("Entitlement leases cannot grant execution authority.");
  }
  if (!["active", "trialing"].includes(payload.subscriptionStatus)) {
    throw new TypeError("Lease subscriptionStatus must be active or trialing.");
  }
  if (payload.featureSet !== plan.code) {
    throw new TypeError("Lease featureSet must match the canonical plan.");
  }
  const canonicalMaxDevices =
    Number.isSafeInteger(plan.maxDevices) && plan.maxDevices >= 0
      ? plan.maxDevices
      : plan.code === "enterprise" && plan.maxDevices === "custom"
        ? null
        : undefined;
  if (canonicalMaxDevices === undefined) {
    throw new TypeError("Canonical plan maxDevices is invalid.");
  }
  const expectedMaxDevices =
    payload.subscriptionStatus === "trialing" ? 1 : canonicalMaxDevices;
  if (payload.maxDevices !== expectedMaxDevices) {
    throw new TypeError("Lease maxDevices does not match its entitlement.");
  }
  if (
    payload.subscriptionStatus === "trialing" &&
    plan.code !== "starter"
  ) {
    throw new TypeError("Trial leases are limited to Starter.");
  }

  const issuedAtMilliseconds = timestampMilliseconds(payload.issuedAt, "issuedAt");
  const renewAfterMilliseconds = timestampMilliseconds(
    payload.renewAfter,
    "renewAfter"
  );
  const expiresAtMilliseconds = timestampMilliseconds(
    payload.expiresAt,
    "expiresAt"
  );
  const graceUntilMilliseconds = timestampMilliseconds(
    payload.graceUntil,
    "graceUntil"
  );
  if (
    !(
      issuedAtMilliseconds < renewAfterMilliseconds &&
      renewAfterMilliseconds < expiresAtMilliseconds &&
      expiresAtMilliseconds <= graceUntilMilliseconds
    )
  ) {
    throw new TypeError(
      "Lease v3 renewal, expiry, and grace deadlines are invalid."
    );
  }
  if (
    renewAfterMilliseconds - issuedAtMilliseconds >
      RUNRAIL_ENTITLEMENT_LEASE_V2_RENEW_AFTER_MILLISECONDS ||
    expiresAtMilliseconds - issuedAtMilliseconds >
      RUNRAIL_ENTITLEMENT_LEASE_V2_DURATION_MILLISECONDS ||
    graceUntilMilliseconds - issuedAtMilliseconds >
      RUNRAIL_ENTITLEMENT_LEASE_V2_GRACE_UNTIL_MILLISECONDS
  ) {
    throw new TypeError("Lease v3 exceeds the bounded Runrail window.");
  }

  if (payload.subscriptionStatus === "active") {
    if (payload.trialStartedAt !== null || payload.trialEndsAt !== null) {
      throw new TypeError("Active leases cannot contain trial timestamps.");
    }
    if (
      renewAfterMilliseconds - issuedAtMilliseconds !==
        RUNRAIL_ENTITLEMENT_LEASE_V2_RENEW_AFTER_MILLISECONDS ||
      expiresAtMilliseconds - issuedAtMilliseconds !==
        RUNRAIL_ENTITLEMENT_LEASE_V2_DURATION_MILLISECONDS ||
      graceUntilMilliseconds - issuedAtMilliseconds !==
        RUNRAIL_ENTITLEMENT_LEASE_V2_GRACE_UNTIL_MILLISECONDS
    ) {
      throw new TypeError(
        "Active lease v3 validity metadata is outside the fixed Runrail window."
      );
    }
  } else {
    const trialStartedAtMilliseconds = timestampMilliseconds(
      payload.trialStartedAt,
      "trialStartedAt"
    );
    const trialEndsAtMilliseconds = timestampMilliseconds(
      payload.trialEndsAt,
      "trialEndsAt"
    );
    if (
      trialStartedAtMilliseconds > issuedAtMilliseconds ||
      trialEndsAtMilliseconds <= issuedAtMilliseconds ||
      graceUntilMilliseconds > trialEndsAtMilliseconds
    ) {
      throw new TypeError(
        "Trial lease validity cannot start before the trial or outlive trial expiry."
      );
    }
  }
  return { issuedAtMilliseconds, expiresAtMilliseconds, graceUntilMilliseconds };
}

function validatePayload(payload) {
  assertObject(payload, "Entitlement lease payload");
  if (
    payload.schemaVersion !== RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION &&
    payload.schemaVersion !== RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V2 &&
    payload.schemaVersion !== RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V3
  ) {
    throw new TypeError("Unsupported entitlement lease schema version.");
  }
  if (payload.catalogRevision !== RUNRAIL_PLAN_CATALOG_REVISION) {
    throw new TypeError("Entitlement lease catalog revision is not current.");
  }
  assertOpaqueId(payload.accountId, "accountId");
  assertOpaqueId(payload.workspaceId, "workspaceId");
  assertOpaqueId(payload.deviceId, "deviceId");
  assertKeyId(payload.keyId);
  if (payload.authority !== RUNRAIL_ENTITLEMENT_LEASE_AUTHORITY) {
    throw new TypeError("Entitlement lease authority is invalid.");
  }
  const plan = validatePlan(payload.planCode);
  assertCanonicalFeatures(payload.features, plan);
  const window =
    payload.schemaVersion === RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION
      ? validateV1Payload(payload)
      : payload.schemaVersion === RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V2
        ? validateV2Payload(payload)
        : validateV3Payload(payload, plan);
  return { plan, ...window };
}

function privateEd25519Key(value) {
  let key;
  try {
    key = value?.type === "private" ? value : createPrivateKey(value);
  } catch {
    throw new TypeError("privateKey must be a valid Ed25519 private key.");
  }
  if (key.type !== "private" || key.asymmetricKeyType !== "ed25519") {
    throw new TypeError("privateKey must be a valid Ed25519 private key.");
  }
  return key;
}

function publicEd25519Key(value) {
  let key;
  try {
    key = value?.type === "public" ? value : createPublicKey(value);
  } catch {
    throw new TypeError("Mapped public key must be a valid Ed25519 public key.");
  }
  if (key.type !== "public" || key.asymmetricKeyType !== "ed25519") {
    throw new TypeError("Mapped public key must be a valid Ed25519 public key.");
  }
  return key;
}

function mappedPublicKey(publicKeysByKeyId, keyId) {
  let value;
  if (publicKeysByKeyId instanceof Map) {
    value = publicKeysByKeyId.get(keyId);
  } else {
    assertObject(publicKeysByKeyId, "publicKeysByKeyId");
    if (Object.prototype.hasOwnProperty.call(publicKeysByKeyId, keyId)) {
      value = publicKeysByKeyId[keyId];
    }
  }
  if (value === undefined) {
    throw new TypeError("No trusted public key is mapped for the lease keyId.");
  }
  return publicEd25519Key(value);
}

function decodeBase64url(value, label) {
  if (typeof value !== "string" || !BASE64URL_PATTERN.test(value)) {
    throw new TypeError(`${label} must be unpadded base64url.`);
  }
  const decoded = Buffer.from(value, "base64url");
  if (decoded.length === 0 || decoded.toString("base64url") !== value) {
    throw new TypeError(`${label} must be canonical unpadded base64url.`);
  }
  return decoded;
}

function frozenPayload(payload, verification = null) {
  return Object.freeze({
    ...payload,
    features: Object.freeze([...payload.features]),
    ...(verification || {})
  });
}

function assertIssuePlan(planCode, contractedEnterprise) {
  const plan = validatePlan(planCode);
  if (typeof contractedEnterprise !== "boolean") {
    throw new TypeError("contractedEnterprise must be a boolean.");
  }
  if (!plan.selfServe && !(plan.code === "enterprise" && contractedEnterprise === true)) {
    throw new TypeError("Enterprise leases require an explicit contractedEnterprise flag.");
  }
  return plan;
}

function signedEnvelope(payload, privateKey) {
  const payloadBytes = Buffer.from(serializeRunrailEntitlementPayload(payload), "utf8");
  const signature = signBytes(null, payloadBytes, privateEd25519Key(privateKey));
  return Object.freeze({
    payload: payloadBytes.toString("base64url"),
    signature: signature.toString("base64url")
  });
}

export function serializeRunrailEntitlementPayload(payload) {
  validatePayload(payload);
  const keys =
    payload.schemaVersion === RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION
      ? V1_PAYLOAD_KEYS
      : payload.schemaVersion === RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V2
        ? V2_PAYLOAD_KEYS
        : V3_PAYLOAD_KEYS;
  return JSON.stringify(Object.fromEntries(keys.map((key) => [key, payload[key]])));
}

export function issueRunrailEntitlementLease(options) {
  assertAllowedKeys(options, V1_ISSUE_KEYS, "Entitlement lease issue options");
  const {
    accountId,
    workspaceId,
    deviceId,
    planCode,
    keyId,
    privateKey,
    contractedEnterprise = false
  } = options;
  assertOpaqueId(accountId, "accountId");
  assertOpaqueId(workspaceId, "workspaceId");
  assertOpaqueId(deviceId, "deviceId");
  assertKeyId(keyId);
  const plan = assertIssuePlan(planCode, contractedEnterprise);
  const issuedAt = timestampFromInput(options.issuedAt ?? new Date(), "issuedAt");
  const expiresAt = timestampFromInput(
    options.expiresAt ?? new Date(Date.parse(issuedAt) + RUNRAIL_ENTITLEMENT_LEASE_MAX_MILLISECONDS),
    "expiresAt"
  );
  return signedEnvelope({
    schemaVersion: RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION,
    catalogRevision: RUNRAIL_PLAN_CATALOG_REVISION,
    accountId,
    workspaceId,
    deviceId,
    planCode: plan.code,
    features: [...plan.features],
    issuedAt,
    expiresAt,
    keyId,
    authority: RUNRAIL_ENTITLEMENT_LEASE_AUTHORITY
  }, privateKey);
}

export function issueRunrailEntitlementLeaseV2(options) {
  assertAllowedKeys(options, V2_ISSUE_KEYS, "Entitlement lease v2 issue options");
  const {
    accountId,
    workspaceId,
    deviceId,
    devicePublicKeySha256,
    leaseSequence,
    planCode,
    keyId,
    privateKey,
    contractedEnterprise = false
  } = options;
  assertOpaqueId(accountId, "accountId");
  assertOpaqueId(workspaceId, "workspaceId");
  assertOpaqueId(deviceId, "deviceId");
  assertSha256(devicePublicKeySha256, "devicePublicKeySha256");
  assertLeaseSequence(leaseSequence);
  assertKeyId(keyId);
  const plan = assertIssuePlan(planCode, contractedEnterprise);
  const issuedAt = timestampFromInput(options.issuedAt ?? new Date(), "issuedAt");
  const issuedAtMilliseconds = Date.parse(issuedAt);
  const renewAfter = timestampFromInput(
    options.renewAfter ??
      new Date(issuedAtMilliseconds + RUNRAIL_ENTITLEMENT_LEASE_V2_RENEW_AFTER_MILLISECONDS),
    "renewAfter"
  );
  const expiresAt = timestampFromInput(
    options.expiresAt ??
      new Date(issuedAtMilliseconds + RUNRAIL_ENTITLEMENT_LEASE_V2_DURATION_MILLISECONDS),
    "expiresAt"
  );
  const graceUntil = timestampFromInput(
    options.graceUntil ??
      new Date(issuedAtMilliseconds + RUNRAIL_ENTITLEMENT_LEASE_V2_GRACE_UNTIL_MILLISECONDS),
    "graceUntil"
  );
  return signedEnvelope({
    schemaVersion: RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V2,
    catalogRevision: RUNRAIL_PLAN_CATALOG_REVISION,
    accountId,
    workspaceId,
    deviceId,
    devicePublicKeySha256,
    leaseSequence,
    planCode: plan.code,
    features: [...plan.features],
    issuedAt,
    renewAfter,
    expiresAt,
    graceUntil,
    keyId,
    authority: RUNRAIL_ENTITLEMENT_LEASE_AUTHORITY,
    executionAuthorityGranted: false
  }, privateKey);
}

export function issueRunrailEntitlementLeaseV3(options) {
  assertAllowedKeys(options, V3_ISSUE_KEYS, "Entitlement lease v3 issue options");
  const {
    accountId,
    workspaceId,
    deviceId,
    devicePublicKeySha256,
    leaseSequence,
    planCode,
    subscriptionStatus = "active",
    trialStartedAt = null,
    trialEndsAt = null,
    keyId,
    privateKey,
    contractedEnterprise = false
  } = options;
  assertOpaqueId(accountId, "accountId");
  assertOpaqueId(workspaceId, "workspaceId");
  assertOpaqueId(deviceId, "deviceId");
  assertSha256(devicePublicKeySha256, "devicePublicKeySha256");
  assertLeaseSequence(leaseSequence);
  assertKeyId(keyId);
  const plan = assertIssuePlan(planCode, contractedEnterprise);
  const canonicalMaxDevices =
    Number.isSafeInteger(plan.maxDevices) && plan.maxDevices >= 0
      ? plan.maxDevices
      : plan.code === "enterprise" && plan.maxDevices === "custom"
        ? null
        : undefined;
  const maxDevices =
    options.maxDevices ??
    (subscriptionStatus === "trialing" ? 1 : canonicalMaxDevices);
  const featureSet = options.featureSet ?? plan.code;
  const issuedAt = timestampFromInput(options.issuedAt ?? new Date(), "issuedAt");
  const issuedAtMilliseconds = Date.parse(issuedAt);
  const trialEndMilliseconds =
    subscriptionStatus === "trialing"
      ? Date.parse(timestampFromInput(trialEndsAt, "trialEndsAt"))
      : null;
  if (
    subscriptionStatus === "trialing" &&
    trialEndMilliseconds - issuedAtMilliseconds < 60 * 1000
  ) {
    throw new TypeError(
      "Trial entitlement must have at least one minute remaining."
    );
  }
  const defaultExpiresAtMilliseconds = Math.min(
    issuedAtMilliseconds + RUNRAIL_ENTITLEMENT_LEASE_V2_DURATION_MILLISECONDS,
    trialEndMilliseconds ?? Number.POSITIVE_INFINITY
  );
  const defaultGraceUntilMilliseconds = Math.min(
    issuedAtMilliseconds + RUNRAIL_ENTITLEMENT_LEASE_V2_GRACE_UNTIL_MILLISECONDS,
    trialEndMilliseconds ?? Number.POSITIVE_INFINITY
  );
  const defaultRenewAfterMilliseconds = Math.min(
    issuedAtMilliseconds +
      RUNRAIL_ENTITLEMENT_LEASE_V2_RENEW_AFTER_MILLISECONDS,
    issuedAtMilliseconds +
      Math.max(1, Math.floor((defaultExpiresAtMilliseconds - issuedAtMilliseconds) / 2))
  );
  const renewAfter = timestampFromInput(
    options.renewAfter ?? new Date(defaultRenewAfterMilliseconds),
    "renewAfter"
  );
  const expiresAt = timestampFromInput(
    options.expiresAt ?? new Date(defaultExpiresAtMilliseconds),
    "expiresAt"
  );
  const graceUntil = timestampFromInput(
    options.graceUntil ?? new Date(defaultGraceUntilMilliseconds),
    "graceUntil"
  );
  return signedEnvelope({
    schemaVersion: RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V3,
    catalogRevision: RUNRAIL_PLAN_CATALOG_REVISION,
    accountId,
    workspaceId,
    deviceId,
    devicePublicKeySha256,
    leaseSequence,
    planCode: plan.code,
    features: [...plan.features],
    subscriptionStatus,
    trialStartedAt:
      subscriptionStatus === "trialing"
        ? timestampFromInput(trialStartedAt, "trialStartedAt")
        : null,
    trialEndsAt:
      subscriptionStatus === "trialing"
        ? timestampFromInput(trialEndsAt, "trialEndsAt")
        : null,
    maxDevices,
    featureSet,
    issuedAt,
    renewAfter,
    expiresAt,
    graceUntil,
    keyId,
    authority: RUNRAIL_ENTITLEMENT_LEASE_AUTHORITY,
    executionAuthorityGranted: false
  }, privateKey);
}

export function verifyRunrailEntitlementLease(lease, options) {
  assertExactKeys(lease, ENVELOPE_KEYS, "Entitlement lease");
  assertAllowedKeys(options, VERIFY_KEYS, "Entitlement lease verify options");
  assertOpaqueId(options.expectedDeviceId, "expectedDeviceId");
  if (options.allowGrace !== undefined && typeof options.allowGrace !== "boolean") {
    throw new TypeError("allowGrace must be a boolean.");
  }

  const payloadBytes = decodeBase64url(lease.payload, "Entitlement lease payload");
  const signature = decodeBase64url(lease.signature, "Entitlement lease signature");
  if (signature.length !== 64) {
    throw new TypeError("Entitlement lease signature must be an Ed25519 signature.");
  }

  let payload;
  try {
    payload = JSON.parse(payloadBytes.toString("utf8"));
  } catch {
    throw new TypeError("Entitlement lease payload must be valid JSON.");
  }
  const {
    issuedAtMilliseconds,
    expiresAtMilliseconds,
    graceUntilMilliseconds
  } = validatePayload(payload);
  const canonicalPayload = serializeRunrailEntitlementPayload(payload);
  if (!payloadBytes.equals(Buffer.from(canonicalPayload, "utf8"))) {
    throw new TypeError("Entitlement lease payload JSON is not canonical.");
  }

  const publicKey = mappedPublicKey(options.publicKeysByKeyId, payload.keyId);
  if (!verifyBytes(null, payloadBytes, publicKey, signature)) {
    throw new TypeError("Entitlement lease signature is invalid.");
  }
  if (payload.deviceId !== options.expectedDeviceId) {
    throw new TypeError("Entitlement lease is not valid for the expected device.");
  }
  const managedLease =
    payload.schemaVersion === RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V2 ||
    payload.schemaVersion === RUNRAIL_ENTITLEMENT_LEASE_SCHEMA_VERSION_V3;
  if (managedLease) {
    assertSha256(options.expectedDevicePublicKeySha256, "expectedDevicePublicKeySha256");
    if (payload.devicePublicKeySha256 !== options.expectedDevicePublicKeySha256) {
      throw new TypeError("Entitlement lease is not valid for the expected device key.");
    }
    if (options.expectedLeaseSequence !== undefined) {
      assertLeaseSequence(options.expectedLeaseSequence, "expectedLeaseSequence");
      if (payload.leaseSequence !== options.expectedLeaseSequence) {
        throw new TypeError("Entitlement lease sequence does not match the expected sequence.");
      }
    }
  }

  const now = timestampFromInput(options.now ?? new Date(), "now");
  const nowMilliseconds = Date.parse(now);
  if (issuedAtMilliseconds > nowMilliseconds) {
    throw new TypeError("Entitlement lease is not valid before its issuance time.");
  }
  if (expiresAtMilliseconds > nowMilliseconds) {
    return frozenPayload(
      payload,
      managedLease
        ? { validity: "valid", offlineGrace: false }
        : null
    );
  }
  if (
    managedLease &&
    options.allowGrace === true &&
    graceUntilMilliseconds > nowMilliseconds
  ) {
    return frozenPayload(payload, { validity: "offline-grace", offlineGrace: true });
  }
  throw new TypeError(
    managedLease &&
      graceUntilMilliseconds <= nowMilliseconds
      ? "Entitlement lease grace deadline has passed."
      : "Entitlement lease has expired."
  );
}
