/**
 * Audit-log hash-chain canonicalisation.
 *
 * Two schemes coexist:
 *
 * - **v1 (legacy, PR-32)**: `prevHash|userId|action|resource|resourceId|details|unix`
 *   — naive `|`-join. In principle vulnerable to a canonicalisation attack
 *   (two semantically-different field sets concatenating to the same
 *   string). In practice the BEFORE INSERT trigger + row-level locks + the
 *   `details` JSON-quoting make a forgery near-impossible. Listed in
 *   AUDIT-2 #6 as "very low severity, no realistic exploit".
 *
 * - **v2 (AUDIT-2 #6, 2026-05-24)**: every field is preceded by an 8-digit
 *   zero-padded LENGTH header, so field boundaries become unambiguous.
 *   `0000000064<prevHash>0000000001<userId>0000000005LOGIN…`. The MySQL
 *   trigger uses `LPAD(LENGTH(x), 8, '0')`; the TypeScript verifier
 *   mirrors this. New rows always use v2; verifyChain tries v2 first and
 *   falls back to v1 for rows written before the migration.
 *
 * Pure functions, no I/O. Tested in
 * `src/lib/services/__tests__/audit-chain.test.ts`.
 */
import { createHash } from 'crypto';

export interface AuditChainInput {
    prevHash: string;
    userId: number;
    action: string;
    resource: string;
    resourceId: string | null;
    details: string | null;
    createdAtUnix: number;
}

function fields(input: AuditChainInput): string[] {
    return [
        input.prevHash,
        String(input.userId),
        input.action,
        input.resource,
        input.resourceId ?? '',
        input.details ?? '',
        String(input.createdAtUnix),
    ];
}

/** Legacy PR-32 scheme: simple pipe-join. */
export function canonicalV1(input: AuditChainInput): string {
    return fields(input).join('|');
}

/**
 * v2 scheme: each field prefixed by an 8-digit length in BYTES (matches
 * MySQL `LPAD(LENGTH(x), 8, '0')` which counts bytes, not chars). The
 * version sentinel `v2:` prefixes the whole string so v1 and v2 outputs
 * never collide.
 */
export function canonicalV2(input: AuditChainInput): string {
    let out = 'v2:';
    for (const f of fields(input)) {
        const byteLen = Buffer.byteLength(f, 'utf8');
        out += byteLen.toString().padStart(8, '0') + f;
    }
    return out;
}

export function computeRowHashV1(input: AuditChainInput): string {
    return createHash('sha256').update(canonicalV1(input)).digest('hex');
}

export function computeRowHashV2(input: AuditChainInput): string {
    return createHash('sha256').update(canonicalV2(input)).digest('hex');
}
