import {
  canonicalize,
  leafHash,
  sha256Json,
  signDomain,
  utf8,
} from "./crypto";
import { INCIDENT_PACKET_PROFILE as PROFILE } from "./constants";
import type { JsonObject, JsonValue, Proof, SignerMaterial } from "./browser-types";

export interface ActionReceiptV04 {
  action: {
    type: string;
    subject: JsonObject;
  };
  anchor_ref: JsonObject;
  authorization: Proof;
  canonicalization: "bulla-jcs-int/1";
  claimed_at: string;
  conventions: JsonValue[];
  diagnostic_ref: { status: "not_applicable" };
  event_id: string;
  evidence_refs: {
    name: string;
    hash: string;
    grounding: "self_asserted" | "counterparty_signed" | "execution_verified";
  }[];
  hashes: {
    content: string;
    event: string;
    attestation: string;
    log_leaf: string;
  };
  kind: "action_receipt";
  mandate: JsonObject;
  occurrence: Proof;
  producer: JsonObject;
  remedy: JsonObject;
  retention: JsonObject;
  schema_version: "0.4";
  signature: Proof;
  stake: null;
}

export interface ReceiptInput {
  actionType: string;
  subject: JsonObject;
  eventId: string;
  claimedAt: string;
  evidence: {
    name: string;
    hash: string;
    grounding: "self_asserted" | "counterparty_signed" | "execution_verified";
  }[];
  signer: SignerMaterial;
  issuerRole: string;
  trustContextRef?: string;
}

export function recourseEnvelope(
  signer: SignerMaterial,
  actionType: string,
  trustContextRef =
    "repository://bulla/spec/control-plane-alpha/contexts/alpha-context.json",
): JsonObject {
  return {
    authority: {
      principal: signer.issuer,
      policy: `policy://bulla-control-plane-alpha/${actionType}`,
      delegation: [],
    },
    bounds: {
      scope: `profile:${PROFILE}`,
    },
    recourse: {
      challenge_window: "P30D",
      forum: {
        log_endpoint: "https://github.com/res-agentica/res-agentica/issues",
        trusted_root_ref: trustContextRef,
      },
      remedies: [
        {
          rung: "challenge",
          verifier: "publish a signed incident.correct receipt",
          anchor: "forum:bulla-control-plane-alpha",
        },
      ],
    },
    retention_class: "operational",
    disclosure_class: "public",
    deed_schema: "0.2",
  };
}

function wireEnvelope(envelope: JsonObject): {
  mandate: JsonObject;
  remedy: JsonObject;
  retention: JsonObject;
} {
  const authority = envelope.authority as JsonObject;
  const bounds = envelope.bounds as JsonObject;
  const remedy = envelope.recourse as JsonObject;
  return {
    mandate: { authority, bounds },
    remedy,
    retention: {
      record: envelope.retention_class as JsonValue,
      disclosure: envelope.disclosure_class as JsonValue,
    },
  };
}

export async function createActionReceiptV04(input: ReceiptInput): Promise<ActionReceiptV04> {
  const envelope = recourseEnvelope(
    input.signer,
    input.actionType,
    input.trustContextRef,
  );
  const contentPreimage: JsonObject = {
    schema_version: "0.4",
    canonicalization: "bulla-jcs-int/1",
    kind: "action_receipt",
    action: {
      type: input.actionType,
      subject: input.subject,
    },
    diagnostic_ref: { status: "not_applicable" },
    evidence_refs: input.evidence,
    anchor_ref: {},
  };
  const contentHash = await sha256Json(contentPreimage);
  const eventHash = await sha256Json({
    content_hash: contentHash,
    event_id: input.eventId,
    claimed_at: input.claimedAt,
  });
  const envelopeHash = await sha256Json(envelope);
  const authorizationHash = await sha256Json({
    event_hash: eventHash,
    envelope_hash: envelopeHash,
  });
  const signature = await signDomain(input.signer, "content", contentHash);
  const occurrence = await signDomain(input.signer, "occurrence", eventHash);
  const authorization = await signDomain(input.signer, "authorization", authorizationHash);
  const attestationHash = await sha256Json({
    content_hash: contentHash,
    signature,
    event_hash: eventHash,
    occurrence,
    recourse_envelope: envelope,
    authorization,
  } as unknown as JsonObject);
  const wire = wireEnvelope(envelope);
  const receipt: ActionReceiptV04 = {
    action: contentPreimage.action as ActionReceiptV04["action"],
    anchor_ref: {},
    authorization,
    canonicalization: "bulla-jcs-int/1",
    claimed_at: input.claimedAt,
    conventions: [],
    diagnostic_ref: { status: "not_applicable" },
    event_id: input.eventId,
    evidence_refs: input.evidence,
    hashes: {
      content: contentHash,
      event: eventHash,
      attestation: attestationHash,
      log_leaf: await leafHash(attestationHash),
    },
    kind: "action_receipt",
    mandate: wire.mandate,
    occurrence,
    producer: {
      bulla_version: "source",
      profile: "bulla.control-plane-alpha/0.1-experimental",
      issuer_role: input.issuerRole,
    },
    remedy: wire.remedy,
    retention: wire.retention,
    schema_version: "0.4",
    signature,
    stake: null,
  };
  if (receiptBytes(receipt).length > 65_536) {
    throw new RangeError("generated ActionReceipt exceeds 65536 bytes");
  }
  return receipt;
}

export function receiptBytes(receipt: ActionReceiptV04): Uint8Array {
  return utf8(`${canonicalize(receipt as unknown as JsonObject)}\n`);
}
