import { hexEncode, hexDecode } from "../../domain/utils/bytes.js";
import CryptoError from "../../domain/errors/CryptoError.js";
import WarpError from "../../domain/errors/WarpError.js";
import BoundaryTransitionRecord, { BTR_VERSION, VerificationResult, validateBTRStructure, } from "../../domain/services/provenance/BTR.js";
import BtrSigningEnvelope from "../../domain/services/provenance/BtrSigningEnvelope.js";
import { ProvenancePayload } from "../../domain/services/provenance/ProvenancePayload.js";
import { serializeFullState, deserializeFullState, computeStateHash, } from "../../domain/services/state/StateSerializer.js";
const HMAC_ALGORITHM = 'sha256';
function stateCodecOptions(stateCodec) {
    return stateCodec === undefined ? {} : { codec: stateCodec };
}
function replayOptions(crypto, stateCodec) {
    return stateCodec === undefined
        ? { crypto }
        : { crypto, stateCodec };
}
function validateHmacKey(key) {
    if (typeof key === 'string' && key.length === 0) {
        throw new CryptoError('Invalid HMAC key: key must not be empty', { code: 'E_INVALID_HMAC_KEY' });
    }
    if (key instanceof Uint8Array && key.byteLength === 0) {
        throw new CryptoError('Invalid HMAC key: key must not be empty', { code: 'E_INVALID_HMAC_KEY' });
    }
}
async function computeAuthenticationTag(envelope, key, deps) {
    const signingBytes = deps.btrCodec.signingBytes(envelope);
    const rawHmac = await deps.crypto.hmac(HMAC_ALGORITHM, key, signingBytes.copyBytes());
    return hexEncode(rawHmac);
}
async function createBTR(initialState, payload, opts) {
    if (!(payload instanceof ProvenancePayload)) {
        throw new WarpError('payload must be a ProvenancePayload', 'E_BTR_INVALID_PAYLOAD');
    }
    validateHmacKey(opts.key);
    const h_in = await computeStateHash(initialState, { crypto: opts.crypto, ...stateCodecOptions(opts.stateCodec) });
    const U_0 = serializeFullState(initialState, stateCodecOptions(opts.stateCodec));
    const finalState = payload.replay(initialState);
    const h_out = await computeStateHash(finalState, { crypto: opts.crypto, ...stateCodecOptions(opts.stateCodec) });
    const envelope = new BtrSigningEnvelope({
        version: BTR_VERSION,
        h_in,
        h_out,
        U_0,
        P: payload.provenance,
        t: opts.timestamp,
    });
    const kappa = await computeAuthenticationTag(envelope, opts.key, {
        crypto: opts.crypto,
        btrCodec: opts.btrCodec,
    });
    return new BoundaryTransitionRecord({
        version: envelope.version,
        h_in: envelope.h_in,
        h_out: envelope.h_out,
        U_0: envelope.U_0,
        P: envelope.provenance,
        t: envelope.t,
        kappa,
    });
}
async function verifyBTR(btr, key, opts = {}) {
    const structError = validateBTRStructure(btr);
    if (structError !== null) {
        return new VerificationResult(false, structError);
    }
    if (opts.crypto === undefined) {
        return new VerificationResult(false, 'CryptoPort required for HMAC verification');
    }
    if (opts.btrCodec === undefined) {
        return new VerificationResult(false, 'BoundaryTransitionRecordCodecPort required for HMAC verification');
    }
    let hmacValid;
    try {
        const expected = await computeAuthenticationTag(btr.envelope, key, { crypto: opts.crypto, btrCodec: opts.btrCodec });
        const actualBuf = hexDecode(btr.kappa);
        const expectedBuf = hexDecode(expected);
        hmacValid = actualBuf.length === expectedBuf.length
            && opts.crypto.timingSafeEqual(actualBuf, expectedBuf);
    }
    catch (err) {
        if (err instanceof RangeError) {
            return new VerificationResult(false, `Invalid hex in authentication tag: ${err.message}`);
        }
        throw err;
    }
    if (!hmacValid) {
        return new VerificationResult(false, 'Authentication tag mismatch');
    }
    if (opts.verifyReplay === true) {
        try {
            const result = await replayBTR(btr, replayOptions(opts.crypto, opts.stateCodec));
            if (result.h_out !== btr.h_out) {
                return new VerificationResult(false, `Replay produced different h_out: expected ${btr.h_out}, got ${result.h_out}`);
            }
        }
        catch (err) {
            return new VerificationResult(false, `Replay failed: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    return new VerificationResult(true);
}
async function replayBTR(btr, deps = {}) {
    const initialState = deserializeFullState(btr.U_0, stateCodecOptions(deps.stateCodec));
    const payload = ProvenancePayload.fromEntries(btr.P);
    const finalState = payload.replay(initialState);
    if (!deps.crypto) {
        throw new CryptoError('CryptoPort required for state hash', { code: 'E_MISSING_CRYPTO' });
    }
    const h_out = await computeStateHash(finalState, { crypto: deps.crypto, ...stateCodecOptions(deps.stateCodec) });
    return { state: finalState, h_out };
}
export { createBTR, verifyBTR, replayBTR };
