import { createHash, verify } from "node:crypto";
import { isRecord } from "../utils/json.js";
export const RUN_INTENT_BRIDGE_RECEIPT_CONTRACT_VERSION = "run-intent-bridge-receipt.v1";
export const RUN_INTENT_RECEIPT_SIGNATURE_PREFIX = "ris_";
const RUN_INTENT_RECEIPT_SIGNATURE_BYTES = 64;
const RUN_INTENT_RECEIPT_ALLOWED_KEYS = new Set([
    "contractVersion",
    "receiptId",
    "keyId",
    "issuer",
    "audience",
    "intentId",
    "workspaceId",
    "workItemId",
    "status",
    "nextStepCode",
    "reasonCode",
    "issuedAt",
    "expiresAt",
    "signature",
]);
const RUN_INTENT_RECEIPT_REQUIRED_KEYS = [
    "contractVersion",
    "receiptId",
    "keyId",
    "issuer",
    "audience",
    "intentId",
    "workspaceId",
    "workItemId",
    "status",
    "nextStepCode",
    "reasonCode",
    "issuedAt",
    "expiresAt",
    "signature",
];
export function createRunIntentReceiptState() {
    return {
        createConsumedReceiptIds: new Set(),
        promptConsumedReceiptIds: new Set(),
        runBindings: new Map(),
        runReceipts: new Map(),
    };
}
export function readRunIntentBridgeReceipt(value, trust, options = {}) {
    if (value === undefined || value === null) {
        return validationError(400, "intent_receipt_missing");
    }
    if (!isRecord(value)) {
        return validationError(400, "intent_receipt_invalid");
    }
    if (Object.keys(value).some((key) => !RUN_INTENT_RECEIPT_ALLOWED_KEYS.has(key))) {
        return validationError(422, "intent_receipt_unsupported");
    }
    if (!hasRequiredReceiptStrings(value)) {
        return validationError(400, "intent_receipt_invalid");
    }
    const receipt = {
        contractVersion: value.contractVersion,
        receiptId: value.receiptId,
        keyId: value.keyId,
        issuer: value.issuer,
        audience: value.audience,
        intentId: value.intentId,
        workspaceId: value.workspaceId,
        workItemId: value.workItemId,
        status: value.status,
        nextStepCode: value.nextStepCode,
        reasonCode: value.reasonCode,
        issuedAt: value.issuedAt,
        expiresAt: value.expiresAt,
        signature: value.signature,
    };
    if (receipt.contractVersion !== RUN_INTENT_BRIDGE_RECEIPT_CONTRACT_VERSION ||
        receipt.status !== "created" ||
        receipt.nextStepCode !== "start-local-bridge-run" ||
        receipt.reasonCode !== "local-bridge-required") {
        return validationError(422, "intent_receipt_unsupported");
    }
    const issuedAtMs = parseReceiptTimestamp(receipt.issuedAt);
    const expiresAtMs = parseReceiptTimestamp(receipt.expiresAt);
    if (issuedAtMs === null || expiresAtMs === null) {
        return validationError(400, "intent_receipt_invalid");
    }
    if (receipt.issuer !== trust.issuer || receipt.audience !== trust.audience) {
        return validationError(400, "intent_receipt_invalid");
    }
    if (!verifyRunIntentBridgeReceiptSignature(receipt, trust)) {
        return validationError(400, "intent_receipt_invalid");
    }
    if (expiresAtMs <= readNowMs(options.now)) {
        return validationError(410, "intent_receipt_expired");
    }
    return { kind: "ok", receipt };
}
export function verifyRunIntentBridgeReceiptSignature(receipt, trust) {
    if (!receipt.signature.startsWith(RUN_INTENT_RECEIPT_SIGNATURE_PREFIX)) {
        return false;
    }
    const trustedKey = trust.trustedPublicKeys.find((key) => key.keyId === receipt.keyId);
    if (!trustedKey)
        return false;
    const signature = base64UrlDecode(receipt.signature.slice(RUN_INTENT_RECEIPT_SIGNATURE_PREFIX.length));
    if (signature.length !== RUN_INTENT_RECEIPT_SIGNATURE_BYTES)
        return false;
    try {
        return verify("sha256", Buffer.from(runIntentBridgeReceiptSigningPayload(receipt), "utf8"), { key: trustedKey.publicKey, dsaEncoding: "ieee-p1363" }, signature);
    }
    catch {
        return false;
    }
}
export function runIntentBridgeReceiptSigningPayload(receipt) {
    return [
        receipt.contractVersion,
        receipt.receiptId,
        receipt.keyId,
        receipt.issuer,
        receipt.audience,
        receipt.intentId,
        receipt.workspaceId,
        receipt.workItemId,
        receipt.status,
        receipt.nextStepCode,
        receipt.reasonCode,
        receipt.issuedAt,
        receipt.expiresAt,
    ].join("\n");
}
export function validateRunIntentBridgeReceiptForCreate(receipt, workspaceId, state) {
    if (receipt.workspaceId !== workspaceId) {
        return { statusCode: 409, reasonCode: "intent_receipt_mismatch" };
    }
    if (state.createConsumedReceiptIds.has(receipt.receiptId)) {
        return { statusCode: 409, reasonCode: "intent_receipt_replayed" };
    }
    return null;
}
export function consumeRunIntentBridgeReceiptForCreate(receipt, state) {
    state.createConsumedReceiptIds.add(receipt.receiptId);
}
export function releaseRunIntentBridgeReceiptForCreate(receipt, state) {
    state.createConsumedReceiptIds.delete(receipt.receiptId);
}
export function bindRunIntentBridgeReceiptToRun(receipt, runId, state) {
    state.runBindings.set(runId, bindingFromReceipt(receipt));
    state.runReceipts.set(runId, { ...receipt });
}
export function releaseRunIntentBridgeReceiptBinding(runId, state) {
    state.runBindings.delete(runId);
    state.runReceipts.delete(runId);
}
export function findRunIntentBridgeReceiptForRun(runId, state) {
    const receipt = state.runReceipts.get(runId);
    return receipt ? { ...receipt } : undefined;
}
export function validateRunIntentBridgeReceiptForPrompt(receipt, target, state) {
    const boundReceipt = state.runBindings.get(target.runId);
    if (!boundReceipt || target.workspaceId !== receipt.workspaceId) {
        return { statusCode: 409, reasonCode: "intent_receipt_mismatch" };
    }
    if (!receiptMatchesBinding(receipt, boundReceipt)) {
        return { statusCode: 409, reasonCode: "intent_receipt_mismatch" };
    }
    if (state.promptConsumedReceiptIds.has(receipt.receiptId)) {
        return { statusCode: 409, reasonCode: "intent_receipt_replayed" };
    }
    return null;
}
export function consumeRunIntentBridgeReceiptForPrompt(receipt, state) {
    state.promptConsumedReceiptIds.add(receipt.receiptId);
}
export function releaseRunIntentBridgeReceiptForPrompt(receipt, state) {
    state.promptConsumedReceiptIds.delete(receipt.receiptId);
}
export function formatRunIntentReceiptRejectionBody(reasonCode) {
    return {
        error: "run_intent_receipt_rejected",
        reasonCode,
        message: runIntentReceiptMessage(reasonCode),
    };
}
export function runIntentReceiptMessage(reasonCode) {
    switch (reasonCode) {
        case "intent_receipt_missing":
            return "Run intent bridge receipt is required for this local bridge action.";
        case "intent_receipt_expired":
            return "Run intent bridge receipt expired before this local bridge action.";
        case "intent_receipt_mismatch":
            return "Run intent bridge receipt does not match this local bridge action.";
        case "intent_receipt_replayed":
            return "Run intent bridge receipt was already used.";
        case "intent_receipt_unsupported":
            return "Run intent bridge receipt is not supported for this local bridge action.";
        case "intent_receipt_invalid":
            return "Run intent bridge receipt is invalid for this local bridge action.";
    }
}
function validationError(statusCode, reasonCode) {
    return { kind: "error", statusCode, reasonCode };
}
function hasRequiredReceiptStrings(value) {
    return RUN_INTENT_RECEIPT_REQUIRED_KEYS.every((key) => typeof value[key] === "string" && value[key].length > 0);
}
function parseReceiptTimestamp(value) {
    const parsed = Date.parse(value);
    return Number.isFinite(parsed) ? parsed : null;
}
function readNowMs(now) {
    if (!now)
        return Date.now();
    const value = now();
    const milliseconds = value instanceof Date ? value.getTime() : value;
    return Number.isFinite(milliseconds) ? milliseconds : Date.now();
}
function base64UrlDecode(value) {
    if (value.length === 0 ||
        value.length % 4 === 1 ||
        !/^[A-Za-z0-9_-]+$/.test(value)) {
        return Buffer.alloc(0);
    }
    try {
        return Buffer.from(value, "base64url");
    }
    catch {
        return Buffer.alloc(0);
    }
}
function bindingFromReceipt(receipt) {
    return {
        receiptId: receipt.receiptId,
        intentId: receipt.intentId,
        workspaceId: receipt.workspaceId,
        workItemId: receipt.workItemId,
        signatureFingerprint: receiptSignatureFingerprint(receipt),
    };
}
function receiptMatchesBinding(receipt, binding) {
    return (binding.receiptId === receipt.receiptId &&
        binding.intentId === receipt.intentId &&
        binding.workspaceId === receipt.workspaceId &&
        binding.workItemId === receipt.workItemId &&
        binding.signatureFingerprint === receiptSignatureFingerprint(receipt));
}
function receiptSignatureFingerprint(receipt) {
    return createHash("sha256").update(receipt.signature).digest("base64url");
}
