/**
 * Healthcare supply-chain audit trail — tamper-evident, hash-chained logging
 * middleware and service for Hono HTTP handlers.
 */
import { createHash } from 'node:crypto';
import { randomUUID } from 'node:crypto';
import { createMiddleware } from 'hono/factory';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
const ENTITY_KEYWORDS = [
    'inventory',
    'forecasts',
    'risk',
    'transfers',
    'procurement',
    'netsuite',
    'dashboard',
];
export function deriveEntityType(path) {
    const segments = path.split('/').filter(Boolean);
    for (const segment of segments) {
        const lower = segment.toLowerCase();
        if (ENTITY_KEYWORDS.includes(lower)) {
            return lower;
        }
    }
    return 'unknown';
}
// ---------------------------------------------------------------------------
// Service
// ---------------------------------------------------------------------------
export class AuditTrailService {
    db;
    lastChecksum = '';
    sequenceNumber = 0;
    constructor(db) {
        this.db = db;
    }
    computeChecksum(record) {
        const payload = JSON.stringify(record) + record.previous_checksum;
        return createHash('sha256').update(payload).digest('hex');
    }
    async recordBefore(correlationId, actor, action, entityType, requestBody, erpWireFormat = null) {
        return this.persist({
            correlationId,
            actor,
            action,
            entityType,
            entityId: null,
            phase: 'before',
            requestBody,
            responseBody: null,
            responseStatus: null,
            durationMs: null,
            success: null,
            erpWireFormat,
        });
    }
    async recordAfter(correlationId, actor, action, entityType, entityId, responseBody, responseStatus, durationMs, success, erpWireFormat = null) {
        return this.persist({
            correlationId,
            actor,
            action,
            entityType,
            entityId,
            phase: 'after',
            requestBody: null,
            responseBody,
            responseStatus,
            durationMs,
            success,
            erpWireFormat,
        });
    }
    async getByCorrelationId(correlationId) {
        const rows = await this.db.query('SELECT * FROM audit_log WHERE correlation_id = $1 ORDER BY sequence_number', [correlationId]);
        return rows;
    }
    async verifyChainIntegrity() {
        const rows = (await this.db.query('SELECT * FROM audit_log ORDER BY sequence_number', []));
        let previousChecksum = '';
        for (const row of rows) {
            const { checksum: _stored, ...rest } = row;
            const expected = this.computeChecksum({ ...rest, previous_checksum: previousChecksum });
            if (expected !== row.checksum) {
                return { valid: false, brokenAt: row.sequence_number };
            }
            previousChecksum = row.checksum;
        }
        return { valid: true };
    }
    // -----------------------------------------------------------------------
    async persist(params) {
        this.sequenceNumber += 1;
        const partial = {
            id: randomUUID(),
            sequence_number: this.sequenceNumber,
            timestamp: new Date().toISOString(),
            correlation_id: params.correlationId,
            actor: params.actor,
            action: params.action,
            entity_type: params.entityType,
            entity_id: params.entityId,
            phase: params.phase,
            request_body: params.requestBody,
            response_body: params.responseBody,
            response_status: params.responseStatus,
            duration_ms: params.durationMs,
            success: params.success,
            erp_wire_format: params.erpWireFormat,
            previous_checksum: this.lastChecksum,
        };
        const checksum = this.computeChecksum(partial);
        const record = { ...partial, checksum };
        this.lastChecksum = checksum;
        await this.db.execute(`INSERT INTO audit_log (
        id, sequence_number, timestamp, correlation_id, actor, action,
        entity_type, entity_id, phase, request_body, response_body,
        response_status, duration_ms, success, erp_wire_format,
        checksum, previous_checksum
      ) VALUES (
        $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17
      )`, [
            record.id,
            record.sequence_number,
            record.timestamp,
            record.correlation_id,
            record.actor,
            record.action,
            record.entity_type,
            record.entity_id,
            record.phase,
            JSON.stringify(record.request_body),
            JSON.stringify(record.response_body),
            record.response_status,
            record.duration_ms,
            record.success,
            JSON.stringify(record.erp_wire_format),
            record.checksum,
            record.previous_checksum,
        ]);
        return record;
    }
}
// ---------------------------------------------------------------------------
// Middleware
// ---------------------------------------------------------------------------
const WRITE_METHODS = new Set(['POST', 'PUT', 'DELETE']);
export function createAuditMiddleware(auditService) {
    return createMiddleware(async (c, next) => {
        if (!WRITE_METHODS.has(c.req.method)) {
            await next();
            return;
        }
        const startTime = Date.now();
        const actor = c.get('iamClaims')?.sub ?? 'anonymous';
        const correlationId = c.get('correlationId') ?? randomUUID();
        const action = `${c.req.method} ${c.req.path}`;
        const entityType = deriveEntityType(c.req.path);
        let requestBody = null;
        try {
            requestBody = await c.req.json();
        }
        catch {
            /* no JSON body */
        }
        try {
            await auditService.recordBefore(correlationId, actor, action, entityType, requestBody);
        }
        catch {
            /* audit must not break request */
        }
        await next();
        let responseBody = null;
        let entityId = null;
        try {
            const cloned = c.res.clone();
            responseBody = await cloned.json();
            if (responseBody &&
                typeof responseBody === 'object' &&
                'id' in responseBody) {
                entityId = String(responseBody.id);
            }
        }
        catch {
            /* non-JSON response */
        }
        const durationMs = Date.now() - startTime;
        const responseStatus = c.res.status;
        const success = responseStatus >= 200 && responseStatus < 400;
        try {
            await auditService.recordAfter(correlationId, actor, action, entityType, entityId, responseBody, responseStatus, durationMs, success);
        }
        catch {
            /* audit must not break request */
        }
    });
}
//# sourceMappingURL=audit-trail.js.map