import {
  base64ToBytes,
  canonicalize,
  leafHash,
  sha256Bytes,
  sha256Json,
  utf8,
  verifyDomain,
} from "./crypto";
import {
  verifyConsistencyProof,
  verifyInclusionProof,
  witnessLeaf,
} from "./merkle";
import type { ActionReceiptV04 } from "./receipt";
import type { CoverageReport, JsonObject, JsonValue, Proof } from "./browser-types";
import { DEFAULT_JSON_LIMITS, parseStrictJsonBytes } from "./strict-json";

export interface ReceiptVerification {
  structure: "VERIFIED" | "FAILED";
  integrity: "VERIFIED" | "FAILED";
  signatures: "VERIFIED" | "FAILED";
  occurrence_binding: "VERIFIED" | "FAILED";
  authority_authenticity: "VERIFIED" | "FAILED" | "NOT_COMPUTED";
  attestation: string | null;
  errors: string[];
}

export interface ReceiptTrustContext {
  expected_issuers: string[];
}

export interface WitnessInclusion {
  attestation: string;
  index: number;
  tree_size: number;
  leaf: string;
  root: string;
  receipt_sha256: string;
  received_at: string;
  proof: string[];
}

export interface WitnessCheckpoint {
  schema_version: 1;
  profile: "bulla.control-plane-alpha/0.1-experimental";
  witness_id: string;
  log_id: string;
  tree_size: number;
  root_hash: string;
  previous_checkpoint_hash: string | null;
  received_at: string;
  witnessed_at: string;
  anchored_before: null;
  checkpoint_hash: string;
  proof: Proof;
}

export interface WitnessConsistency {
  old_size: number;
  new_size: number;
  old_root: string;
  new_root: string;
  proof: string[];
}

export interface CoverageInput {
  decision_denominator?: string[];
  effect_denominator?: string[];
  decision_receipts: unknown[];
  observation_receipts: unknown[];
}

function object(value: unknown): JsonObject | null {
  return value !== null && typeof value === "object" && !Array.isArray(value)
    ? (value as JsonObject)
    : null;
}

function exactKeys(value: JsonObject, expected: readonly string[]): boolean {
  const actual = Object.keys(value).sort();
  return actual.length === expected.length && actual.every((key, index) => key === [...expected].sort()[index]);
}

function closedKeys(
  value: JsonObject,
  allowed: readonly string[],
  required: readonly string[] = [],
): boolean {
  const actual = Object.keys(value);
  return (
    actual.every((key) => allowed.includes(key)) &&
    required.every((key) => Object.hasOwn(value, key))
  );
}

function nonEmptyString(value: JsonValue | undefined): boolean {
  return typeof value === "string" && value.trim().length > 0;
}

function validExecutableDefinition(value: JsonValue | undefined): boolean {
  const definition = object(value);
  const schema = definition === null ? null : object(definition.schema);
  if (
    definition === null ||
    !closedKeys(definition, ["form", "schema", "quantum"], ["form", "schema"]) ||
    definition.form !== "jsonschema+quantum/1" ||
    schema === null ||
    !closedKeys(
      schema,
      ["type", "properties", "required", "additionalProperties"],
    ) ||
    (schema.type ?? "object") !== "object"
  ) {
    return false;
  }
  const propertiesValue = schema.properties ?? {};
  const properties = object(propertiesValue);
  const required = schema.required ?? [];
  if (
    properties === null ||
    !Array.isArray(required) ||
    required.some((item) => typeof item !== "string" || item.trim().length === 0) ||
    new Set(required).size !== required.length ||
    typeof (schema.additionalProperties ?? true) !== "boolean"
  ) {
    return false;
  }
  for (const [name, raw] of Object.entries(properties)) {
    const property = object(raw);
    const propertyType = property?.type;
    if (
      name.trim().length === 0 ||
      property === null ||
      !closedKeys(
        property,
        ["type", "enum", "const", "minimum", "maximum", "pattern"],
      ) ||
      (propertyType !== undefined &&
        !["string", "integer", "number", "boolean"].includes(
          propertyType as string,
        )) ||
      (Object.hasOwn(property, "enum") &&
        (!Array.isArray(property.enum) || property.enum.length === 0))
    ) {
      return false;
    }
    for (const keyword of ["minimum", "maximum"] as const) {
      if (
        Object.hasOwn(property, keyword) &&
        (typeof property[keyword] !== "number" ||
          !Number.isSafeInteger(property[keyword]) ||
          !["integer", "number"].includes(propertyType as string))
      ) {
        return false;
      }
    }
    if (
      typeof property.minimum === "number" &&
      typeof property.maximum === "number" &&
      property.minimum > property.maximum
    ) {
      return false;
    }
    if (
      Object.hasOwn(property, "pattern") &&
      (propertyType !== "string" || typeof property.pattern !== "string")
    ) {
      return false;
    }
    if (typeof property.pattern === "string") {
      try {
        new RegExp(property.pattern);
      } catch {
        return false;
      }
    }
  }
  if (definition.quantum !== undefined && definition.quantum !== null) {
    const quantum = object(definition.quantum);
    if (quantum === null) return false;
    for (const [name, raw] of Object.entries(quantum)) {
      const constraint = object(raw);
      if (
        name.trim().length === 0 ||
        constraint === null ||
        !closedKeys(constraint, ["unit", "multipleOf"], ["unit"]) ||
        !nonEmptyString(constraint.unit) ||
        (constraint.multipleOf !== undefined &&
          (!Number.isSafeInteger(constraint.multipleOf) ||
            (constraint.multipleOf as number) < 1)) ||
        object(properties[name])?.type !== "integer"
      ) {
        return false;
      }
    }
  }
  return true;
}

async function validContentStructure(
  receipt: ActionReceiptV04,
  action: JsonObject | null,
  hashes: JsonObject | null,
): Promise<boolean> {
  const diagnostic = object(receipt.diagnostic_ref);
  const validDiagnostic =
    diagnostic !== null &&
    closedKeys(diagnostic, ["status", "ref"], ["status"]) &&
    ["reference", "not_applicable", "deferred"].includes(
      diagnostic.status as string,
    ) &&
    (diagnostic.status !== "reference" || nonEmptyString(diagnostic.ref));
  const validEvidence =
    Array.isArray(receipt.evidence_refs) &&
    receipt.evidence_refs.every((raw) => {
      const item = object(raw);
      return (
        item !== null &&
        exactKeys(item, ["name", "hash", "grounding"]) &&
        nonEmptyString(item.name) &&
        nonEmptyString(item.hash) &&
        [
          "self_asserted",
          "counterparty_signed",
          "third_party_anchored",
          "execution_verified",
        ].includes(item.grounding as string)
      );
    });
  let validConventions = Array.isArray(receipt.conventions);
  if (validConventions) {
    for (const raw of receipt.conventions) {
      const convention = object(raw);
      if (
        convention === null ||
        !closedKeys(
          convention,
          [
            "name",
            "scope",
            "kind",
            "definition",
            "definition_hash",
            "forum",
          ],
          ["name", "scope", "kind", "definition_hash"],
        ) ||
        !nonEmptyString(convention.name) ||
        !nonEmptyString(convention.scope) ||
        !["executable", "semantic"].includes(convention.kind as string) ||
        typeof convention.definition_hash !== "string" ||
        !/^sha256:[0-9a-f]{64}$/u.test(convention.definition_hash)
      ) {
        validConventions = false;
        break;
      }
      if (convention.kind === "executable") {
        if (
          !Object.hasOwn(convention, "definition") ||
          !validExecutableDefinition(convention.definition) ||
          (await sha256Json(convention.definition as JsonValue)) !==
            convention.definition_hash
        ) {
          validConventions = false;
          break;
        }
        continue;
      }
      const forum = object(convention.forum);
      if (
        forum === null ||
        !exactKeys(forum, ["log_endpoint", "trusted_root_ref"]) ||
        !nonEmptyString(forum.log_endpoint) ||
        !nonEmptyString(forum.trusted_root_ref) ||
        (Object.hasOwn(convention, "definition") &&
          (typeof convention.definition !== "string" ||
            (await sha256Bytes(utf8(convention.definition))) !==
              convention.definition_hash))
      ) {
        validConventions = false;
        break;
      }
    }
  }
  return (
    action !== null &&
    exactKeys(action, ["subject", "type"]) &&
    nonEmptyString(action.type) &&
    object(action.subject) !== null &&
    validDiagnostic &&
    validEvidence &&
    object(receipt.anchor_ref) !== null &&
    validConventions &&
    receipt.stake === null &&
    typeof receipt.claimed_at === "string" &&
    receipt.claimed_at.length > 0 &&
    typeof receipt.event_id === "string" &&
    /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(
      receipt.event_id,
    ) &&
    hashes !== null &&
    exactKeys(hashes, ["attestation", "content", "event", "log_leaf"]) &&
    Object.values(hashes).every(
      (value) =>
        typeof value === "string" && /^sha256:[0-9a-f]{64}$/u.test(value),
    )
  );
}

function validEnvelopeStructure(
  receipt: ActionReceiptV04,
  issuer: string,
): boolean {
  const mandate = object(receipt.mandate);
  const authority = mandate === null ? null : object(mandate.authority);
  const bounds = mandate === null ? null : object(mandate.bounds);
  const remedy = object(receipt.remedy);
  const forum = remedy === null ? null : object(remedy.forum);
  const retention = object(receipt.retention);
  const deedSchema = mandate?.deed_schema ?? "0.2";
  const delegation = authority?.delegation;
  const delegationValid =
    Array.isArray(delegation) &&
    (deedSchema === "0.2"
      ? delegation.every((item) => typeof item === "string")
      : deedSchema === "0.3" &&
        delegation.every((raw) => {
          const grant = object(raw);
          const proof = grant === null ? null : object(grant.proof);
          const checkpointValid = (name: "not_before" | "not_after") => {
            if (!Object.hasOwn(grant as JsonObject, name)) return true;
            const checkpoint = object((grant as JsonObject)[name]);
            return (
              checkpoint !== null &&
              exactKeys(checkpoint, ["domain", "value"])
            );
          };
          return (
            grant !== null &&
            closedKeys(
              grant,
              [
                "grantor",
                "grantee",
                "principal",
                "parent",
                "policy_digest",
                "scope_digest",
                "not_before",
                "not_after",
                "proof",
              ],
              [
                "grantor",
                "grantee",
                "principal",
                "parent",
                "policy_digest",
                "scope_digest",
                "proof",
              ],
            ) &&
            proof !== null &&
            exactKeys(proof, [
              "type",
              "purpose",
              "issuer",
              "verificationMethod",
              "proofValue",
            ]) &&
            checkpointValid("not_before") &&
            checkpointValid("not_after")
          );
        }));
  if (
    mandate === null ||
    !closedKeys(mandate, ["deed_schema", "authority", "bounds"], [
      "authority",
      "bounds",
    ]) ||
    !["0.2", "0.3"].includes(deedSchema as string) ||
    authority === null ||
    !exactKeys(authority, ["principal", "policy", "delegation"]) ||
    !nonEmptyString(authority.principal) ||
    !nonEmptyString(authority.policy) ||
    !delegationValid ||
    bounds === null ||
    !closedKeys(bounds, ["scope", "expires", "rollback_window"], ["scope"]) ||
    !nonEmptyString(bounds.scope) ||
    remedy === null ||
    !exactKeys(remedy, ["challenge_window", "forum", "remedies"]) ||
    !nonEmptyString(remedy.challenge_window) ||
    forum === null ||
    !exactKeys(forum, ["log_endpoint", "trusted_root_ref"]) ||
    !nonEmptyString(forum.log_endpoint) ||
    !nonEmptyString(forum.trusted_root_ref) ||
    !Array.isArray(remedy.remedies) ||
    remedy.remedies.length === 0 ||
    retention === null ||
    !exactKeys(retention, ["record", "disclosure"]) ||
    !["authority-permanent", "operational", "personal-expiring"].includes(
      retention.record as string,
    ) ||
    !["public", "party", "auditor"].includes(retention.disclosure as string)
  ) {
    return false;
  }
  return remedy.remedies.every((raw) => {
    const item = object(raw);
    return (
      item !== null &&
      exactKeys(item, ["rung", "verifier", "anchor"]) &&
      [
        "recompute",
        "challenge",
        "cure",
        "revert",
        "slash",
        "escalate",
      ].includes(item.rung as string) &&
      nonEmptyString(item.verifier) &&
      nonEmptyString(item.anchor)
    );
  });
}

function decodeBase58(value: string): Uint8Array {
  const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
  const bytes = [0];
  for (const character of value) {
    const index = alphabet.indexOf(character);
    if (index < 0) throw new TypeError("invalid base58btc character");
    let carry = index;
    for (let cursor = 0; cursor < bytes.length; cursor += 1) {
      carry += (bytes[cursor] as number) * 58;
      bytes[cursor] = carry & 0xff;
      carry >>= 8;
    }
    while (carry > 0) {
      bytes.push(carry & 0xff);
      carry >>= 8;
    }
  }
  bytes.reverse();
  for (const character of value) {
    if (character !== "1") break;
    bytes.unshift(0);
  }
  return Uint8Array.from(bytes);
}

async function publicJwkFromDidKey(did: string): Promise<JsonWebKey> {
  const prefix = "did:key:z";
  if (!did.startsWith(prefix)) throw new TypeError("proof issuer is not an Ed25519 did:key");
  const decoded = decodeBase58(did.slice(prefix.length));
  if (decoded.length !== 34 || decoded[0] !== 0xed || decoded[1] !== 0x01) {
    throw new TypeError("proof issuer has the wrong multicodec");
  }
  const x = btoa(String.fromCharCode(...decoded.slice(2)))
    .replaceAll("+", "-")
    .replaceAll("/", "_")
    .replace(/=+$/u, "");
  return { kty: "OKP", crv: "Ed25519", x, ext: true };
}

function envelopeFromWire(receipt: ActionReceiptV04): JsonObject {
  const mandate = object(receipt.mandate) as JsonObject;
  const retention = object(receipt.retention) as JsonObject;
  return {
    deed_schema: (mandate.deed_schema ?? "0.2") as JsonValue,
    authority: mandate.authority as JsonValue,
    bounds: mandate.bounds as JsonValue,
    recourse: receipt.remedy,
    retention_class: retention.record as JsonValue,
    disclosure_class: retention.disclosure as JsonValue,
  };
}

export async function verifyActionReceiptV04(
  receiptValue: unknown,
  context?: ReceiptTrustContext,
): Promise<ReceiptVerification> {
  const errors: string[] = [];
  const candidate = object(receiptValue);
  if (
    candidate === null ||
    candidate.schema_version !== "0.4" ||
    candidate.kind !== "action_receipt" ||
    candidate.canonicalization !== "bulla-jcs-int/1"
  ) {
    return {
      structure: "FAILED",
      integrity: "FAILED",
      signatures: "FAILED",
      occurrence_binding: "FAILED",
      authority_authenticity: context === undefined ? "NOT_COMPUTED" : "FAILED",
      attestation: null,
      errors: ["receipt is not a supported ActionReceipt v0.4 record"],
    };
  }
  const receipt = candidate as unknown as ActionReceiptV04;
  try {
    const receiptFields = [
      "action",
      "anchor_ref",
      "authorization",
      "canonicalization",
      "claimed_at",
      "conventions",
      "diagnostic_ref",
      "event_id",
      "evidence_refs",
      "hashes",
      "kind",
      "mandate",
      "occurrence",
      "producer",
      "remedy",
      "retention",
      "schema_version",
      "signature",
      "stake",
    ];
    const proofFields = ["issuer", "proofValue", "purpose", "type", "verificationMethod"];
    const action = object(receipt.action);
    const hashes = object(receipt.hashes);
    const proofs = [receipt.signature, receipt.occurrence, receipt.authorization];
    const purposes: Proof["purpose"][] = ["content", "occurrence", "authorization"];
    const issuer = receipt.signature.issuer;
    const envelopeStructureValid = validEnvelopeStructure(receipt, issuer);
    const contentStructureValid = await validContentStructure(
      receipt,
      action,
      hashes,
    );
    const structureValid =
      exactKeys(candidate, receiptFields) &&
      contentStructureValid &&
      envelopeStructureValid &&
      proofs.every(
        (proof, index) =>
          object(proof) !== null &&
          exactKeys(proof as unknown as JsonObject, proofFields) &&
          proof.type === "bulla/ed25519-2026" &&
          proof.purpose === purposes[index],
      );
    if (!structureValid) errors.push("receipt or proof structure is not closed");
    const contentPreimage: JsonObject = {
      schema_version: "0.4",
      canonicalization: "bulla-jcs-int/1",
      kind: "action_receipt",
      action: receipt.action,
      diagnostic_ref: receipt.diagnostic_ref,
      evidence_refs: receipt.evidence_refs,
      anchor_ref: receipt.anchor_ref,
    };
    if (receipt.conventions.length > 0) contentPreimage.conventions = receipt.conventions;
    const content = await sha256Json(contentPreimage);
    const event = await sha256Json({
      content_hash: content,
      event_id: receipt.event_id,
      claimed_at: receipt.claimed_at,
    });
    const envelope = envelopeFromWire(receipt);
    const envelopeHash = await sha256Json(envelope);
    const authorizationHash = await sha256Json({
      event_hash: event,
      envelope_hash: envelopeHash,
    });
    const attestation = await sha256Json({
      content_hash: content,
      signature: receipt.signature,
      event_hash: event,
      occurrence: receipt.occurrence,
      recourse_envelope: envelope,
      authorization: receipt.authorization,
    } as unknown as JsonObject);
    const logLeaf = await leafHash(attestation);
    if (
      receipt.hashes.content !== content ||
      receipt.hashes.event !== event ||
      receipt.hashes.attestation !== attestation ||
      receipt.hashes.log_leaf !== logLeaf
    ) {
      errors.push("one or more receipt hashes do not recompute");
    }
    const mandate = object(receipt.mandate);
    const authority = mandate === null ? null : object(mandate.authority);
    const principalBindingValid =
      authority !== null &&
      (mandate?.deed_schema ?? "0.2") === "0.2" &&
      Array.isArray(authority.delegation) &&
      authority.delegation.length === 0 &&
      authority.principal === issuer;
    if (!principalBindingValid) {
      errors.push(
        "incident profile requires direct deed_schema 0.2 authority with an empty delegation and principal equal to the accepted signer",
      );
    }
    const signerTopologyValid =
      receipt.occurrence.issuer !== issuer ||
      receipt.authorization.issuer !== issuer ||
      receipt.signature.verificationMethod !== issuer ||
      receipt.occurrence.verificationMethod !== issuer ||
      receipt.authorization.verificationMethod !== issuer
        ? false
        : true;
    if (!signerTopologyValid) {
      errors.push("receipt proof set does not name one did:key signer");
    }
    const publicJwk = await publicJwkFromDidKey(issuer);
    const contentProof = await verifyDomain(publicJwk, receipt.signature, "content", content);
    const occurrenceProof = await verifyDomain(publicJwk, receipt.occurrence, "occurrence", event);
    const authorizationProof = await verifyDomain(
      publicJwk,
      receipt.authorization,
      "authorization",
      authorizationHash,
    );
    if (!contentProof) errors.push("content proof failed");
    if (!occurrenceProof) errors.push("occurrence proof failed");
    if (!authorizationProof) errors.push("authorization proof failed");
    return {
      structure: structureValid ? "VERIFIED" : "FAILED",
      integrity: errors.some((error) => error.includes("hash")) ? "FAILED" : "VERIFIED",
      signatures:
        structureValid && signerTopologyValid && contentProof && occurrenceProof && authorizationProof
          ? "VERIFIED"
          : "FAILED",
      occurrence_binding: occurrenceProof ? "VERIFIED" : "FAILED",
      authority_authenticity:
        context === undefined
          ? "NOT_COMPUTED"
          : structureValid &&
              signerTopologyValid &&
              principalBindingValid &&
              authorizationProof &&
              context.expected_issuers.includes(issuer)
            ? "VERIFIED"
            : "FAILED",
      attestation,
      errors,
    };
  } catch (error) {
    errors.push(error instanceof Error ? error.message : "receipt verification failed");
    return {
      structure: "FAILED",
      integrity: "FAILED",
      signatures: "FAILED",
      occurrence_binding: "FAILED",
      authority_authenticity: context === undefined ? "NOT_COMPUTED" : "FAILED",
      attestation: null,
      errors,
    };
  }
}

export async function verifyActionReceiptV04Bytes(
  bytes: Uint8Array,
  context?: ReceiptTrustContext,
): Promise<ReceiptVerification> {
  try {
    const value = parseStrictJsonBytes(bytes, {
      ...DEFAULT_JSON_LIMITS,
      maxBytes: 65_536,
    });
    return verifyActionReceiptV04(value, context);
  } catch (error) {
    return {
      structure: "FAILED",
      integrity: "FAILED",
      signatures: "FAILED",
      occurrence_binding: "FAILED",
      authority_authenticity: context === undefined ? "NOT_COMPUTED" : "FAILED",
      attestation: null,
      errors: [error instanceof Error ? error.message : "strict receipt parsing failed"],
    };
  }
}

export async function verifyWitnessInclusion(
  receiptBytes: Uint8Array,
  inclusion: WitnessInclusion,
): Promise<boolean> {
  const inclusionValue = object(inclusion);
  if (
    inclusionValue === null ||
    !exactKeys(inclusionValue, [
      "attestation",
      "index",
      "tree_size",
      "leaf",
      "root",
      "receipt_sha256",
      "received_at",
      "proof",
    ]) ||
    typeof inclusion.attestation !== "string" ||
    !/^sha256:[0-9a-f]{64}$/u.test(inclusion.attestation) ||
    !Number.isSafeInteger(inclusion.index) ||
    !Number.isSafeInteger(inclusion.tree_size) ||
    inclusion.tree_size < 1 ||
    typeof inclusion.leaf !== "string" ||
    !/^sha256:[0-9a-f]{64}$/u.test(inclusion.leaf) ||
    typeof inclusion.root !== "string" ||
    !/^sha256:[0-9a-f]{64}$/u.test(inclusion.root) ||
    typeof inclusion.receipt_sha256 !== "string" ||
    !/^sha256:[0-9a-f]{64}$/u.test(inclusion.receipt_sha256) ||
    typeof inclusion.received_at !== "string" ||
    !Array.isArray(inclusion.proof) ||
    inclusion.proof.some(
      (item) => typeof item !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(item),
    ) ||
    inclusion.index < 0 ||
    inclusion.index >= inclusion.tree_size
  ) {
    return false;
  }
  if ((await sha256Bytes(receiptBytes)) !== inclusion.receipt_sha256) return false;
  let receipt: unknown;
  try {
    receipt = parseStrictJsonBytes(receiptBytes, {
      ...DEFAULT_JSON_LIMITS,
      maxBytes: 65_536,
    });
  } catch {
    return false;
  }
  const record = object(receipt);
  const hashes = record === null ? null : object(record.hashes);
  if (hashes === null || hashes.attestation !== inclusion.attestation) return false;
  const contentHash = hashes.content;
  const signature = record === null ? null : object(record.signature);
  if (typeof contentHash !== "string" || signature === null || typeof signature.issuer !== "string") {
    return false;
  }
  const leaf = await witnessLeaf({
    issuer: signature.issuer,
    content_hash: contentHash,
    attestation_hash: inclusion.attestation,
  });
  return (
    leaf === inclusion.leaf &&
    (await verifyInclusionProof(leaf, inclusion.index, inclusion.tree_size, inclusion.proof, inclusion.root))
  );
}

function checkpointPreimage(checkpoint: WitnessCheckpoint): JsonObject {
  return {
    schema_version: checkpoint.schema_version,
    profile: checkpoint.profile,
    witness_id: checkpoint.witness_id,
    log_id: checkpoint.log_id,
    tree_size: checkpoint.tree_size,
    root_hash: checkpoint.root_hash,
    previous_checkpoint_hash: checkpoint.previous_checkpoint_hash,
    received_at: checkpoint.received_at,
    witnessed_at: checkpoint.witnessed_at,
    anchored_before: checkpoint.anchored_before,
  };
}

export async function verifyWitnessCheckpoint(
  checkpoint: WitnessCheckpoint,
  expectedWitnessDidKey: string,
): Promise<boolean> {
  const candidate = object(checkpoint);
  const proof = object(checkpoint.proof);
  if (
    candidate === null ||
    !exactKeys(candidate, [
      "schema_version",
      "profile",
      "witness_id",
      "log_id",
      "tree_size",
      "root_hash",
      "previous_checkpoint_hash",
      "received_at",
      "witnessed_at",
      "anchored_before",
      "checkpoint_hash",
      "proof",
    ]) ||
    checkpoint.schema_version !== 1 ||
    checkpoint.profile !== "bulla.control-plane-alpha/0.1-experimental" ||
    checkpoint.witness_id !== "bulla-control-plane-alpha-witness" ||
    checkpoint.log_id !== "bulla-control-plane-alpha-receipt-log" ||
    !Number.isSafeInteger(checkpoint.tree_size) ||
    checkpoint.tree_size < 0 ||
    !/^sha256:[0-9a-f]{64}$/u.test(checkpoint.root_hash) ||
    (checkpoint.previous_checkpoint_hash !== null &&
      !/^sha256:[0-9a-f]{64}$/u.test(checkpoint.previous_checkpoint_hash)) ||
    typeof checkpoint.received_at !== "string" ||
    typeof checkpoint.witnessed_at !== "string" ||
    checkpoint.anchored_before !== null ||
    !/^sha256:[0-9a-f]{64}$/u.test(checkpoint.checkpoint_hash) ||
    proof === null ||
    !exactKeys(proof, [
      "type",
      "purpose",
      "issuer",
      "verificationMethod",
      "proofValue",
    ]) ||
    checkpoint.proof.type !== "bulla/ed25519-2026" ||
    checkpoint.proof.purpose !== "witness-checkpoint" ||
    typeof checkpoint.proof.proofValue !== "string"
  ) {
    return false;
  }
  const digest = await sha256Json(checkpointPreimage(checkpoint));
  if (digest !== checkpoint.checkpoint_hash) return false;
  if (
    checkpoint.proof.issuer !== expectedWitnessDidKey ||
    checkpoint.proof.verificationMethod !== expectedWitnessDidKey
  ) {
    return false;
  }
  return verifyDomain(
    await publicJwkFromDidKey(expectedWitnessDidKey),
    checkpoint.proof,
    "witness-checkpoint",
    checkpoint.checkpoint_hash,
    "0.4",
  );
}

export async function verifyCheckpointConsistency(
  previous: WitnessCheckpoint | null,
  current: WitnessCheckpoint,
  proof: WitnessConsistency,
  expectedWitnessDidKey: string,
): Promise<boolean> {
  const candidate = object(proof);
  if (
    candidate === null ||
    !exactKeys(candidate, [
      "old_size",
      "new_size",
      "old_root",
      "new_root",
      "proof",
    ]) ||
    !Number.isSafeInteger(proof.old_size) ||
    !Number.isSafeInteger(proof.new_size) ||
    proof.old_size < 0 ||
    proof.new_size < proof.old_size ||
    !/^sha256:[0-9a-f]{64}$/u.test(proof.old_root) ||
    !/^sha256:[0-9a-f]{64}$/u.test(proof.new_root) ||
    !Array.isArray(proof.proof) ||
    proof.proof.some(
      (item) => typeof item !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(item),
    )
  ) {
    return false;
  }
  if (!(await verifyWitnessCheckpoint(current, expectedWitnessDidKey))) return false;
  if (previous === null) {
    return (
      proof.old_size === 0 &&
      proof.old_root === (await sha256Bytes(new Uint8Array())) &&
      proof.new_size === current.tree_size &&
      proof.new_root === current.root_hash &&
      proof.proof.length === 0
    );
  }
  if (!(await verifyWitnessCheckpoint(previous, expectedWitnessDidKey))) return false;
  if (
    proof.old_size !== previous.tree_size ||
    proof.new_size !== current.tree_size ||
    proof.old_root !== previous.root_hash ||
    proof.new_root !== current.root_hash ||
    (current.tree_size === previous.tree_size + 1 &&
      current.previous_checkpoint_hash !== previous.checkpoint_hash)
  ) {
    return false;
  }
  return (
    await verifyConsistencyProof(
      proof.old_size,
      proof.new_size,
      proof.proof,
      proof.old_root,
      proof.new_root,
    )
  );
}

function subjectString(receipt: unknown, type: string, field: string): string | null {
  const record = object(receipt);
  const action = record === null ? null : object(record.action);
  const subject = action === null ? null : object(action.subject);
  return action?.type === type && typeof subject?.[field] === "string"
    ? (subject[field] as string)
    : null;
}

function dimension(
  anchorId: string,
  denominator: string[] | undefined,
  receipted: Set<string>,
): CoverageReport["decision"] {
  if (denominator === undefined) {
    return {
      anchor_id: anchorId,
      status: "NOT_COMPUTED",
      receipted: null,
      total: null,
      uncovered: [],
      reasons: ["DENOMINATOR_MISSING"],
    };
  }
  if (denominator.length === 0) {
    return {
      anchor_id: anchorId,
      status: "NOT_COMPUTED",
      receipted: null,
      total: null,
      uncovered: [],
      reasons: ["DENOMINATOR_EMPTY"],
    };
  }
  if (new Set(denominator).size !== denominator.length) {
    return {
      anchor_id: anchorId,
      status: "NOT_COMPUTED",
      receipted: null,
      total: null,
      uncovered: [],
      reasons: ["DENOMINATOR_DUPLICATE_ID"],
    };
  }
  return {
    anchor_id: anchorId,
    status: "COMPUTED",
    receipted: denominator.filter((identifier) => receipted.has(identifier)).length,
    total: denominator.length,
    uncovered: denominator.filter((identifier) => !receipted.has(identifier)),
    reasons: [],
  };
}

export function recomputeCoverage(input: CoverageInput): CoverageReport {
  const decisions = new Set(
    input.decision_receipts
      .map((receipt) => subjectString(receipt, "capability.decide", "decision_event_id"))
      .filter((value): value is string => value !== null),
  );
  const effects = new Set(
    input.observation_receipts
      .map((receipt) => subjectString(receipt, "capability.observe", "observation_id"))
      .filter((value): value is string => value !== null),
  );
  return {
    decision: dimension("mcp-boundary-ingress", input.decision_denominator, decisions),
    effect: dimension("synthetic-receiver-effect", input.effect_denominator, effects),
  };
}

export { canonicalize };
export { parseStrictJsonBytes };
export type { JsonLimits } from "./strict-json";
export type { CoverageReport };
