// Journal d'audit : trace persistante des opérations sensibles (création/révocation de tokens,
// changements de réglages, génération de secrets, abonnements push, CRUD groupes). Exigence de
// conformité entreprise (GDPR/SOX/ISO 27001). Règle ABSOLUE : ne JAMAIS journaliser un secret en
// clair — `details` ne contient que des métadonnées non sensibles (ids, labels, compteurs).
import { randomUUID } from 'node:crypto';
import type { AuditLogEntry } from '@arboretum/shared';
import type { Db } from '../db/index.js';

export type AuditResult = 'ok' | 'denied' | 'error';

export interface AuditEntry {
  /** tokenId de l'acteur, ou 'system' (opérations automatiques), ou 'anonymous' (avant auth). */
  actor: string;
  /** verbe.objet, ex. 'token.create', 'settings.update', 'login.failure'. */
  action: string;
  resourceId?: string | null;
  details?: Record<string, unknown> | null;
  result?: AuditResult;
}

/** Enregistre une entrée d'audit. Best-effort : une erreur d'écriture ne casse jamais l'opération métier. */
export function recordAudit(db: Db, e: AuditEntry): void {
  try {
    db.prepare(
      'INSERT INTO audit_logs (id, ts, actor, action, resource_id, details, result) VALUES (?, ?, ?, ?, ?, ?, ?)',
    ).run(
      randomUUID(),
      new Date().toISOString(),
      e.actor,
      e.action,
      e.resourceId ?? null,
      e.details ? JSON.stringify(e.details) : null,
      e.result ?? 'ok',
    );
  } catch {
    /* l'audit ne doit jamais faire échouer l'action auditée */
  }
}

/** Liste paginée par date décroissante (curseur `before` = ts strictement inférieur). */
export function listAudit(db: Db, opts: { limit: number; before?: string | null }): AuditLogEntry[] {
  const limit = Math.min(Math.max(Math.trunc(opts.limit) || 50, 1), 200);
  const rows = (
    opts.before
      ? db
          .prepare(
            'SELECT id, ts, actor, action, resource_id AS resourceId, details, result FROM audit_logs WHERE ts < ? ORDER BY ts DESC LIMIT ?',
          )
          .all(opts.before, limit)
      : db
          .prepare(
            'SELECT id, ts, actor, action, resource_id AS resourceId, details, result FROM audit_logs ORDER BY ts DESC LIMIT ?',
          )
          .all(limit)
  ) as Array<{ id: string; ts: string; actor: string; action: string; resourceId: string | null; details: string | null; result: string }>;
  return rows.map((r) => ({ ...r, details: r.details ? safeParse(r.details) : null }));
}

function safeParse(s: string): unknown {
  try {
    return JSON.parse(s);
  } catch {
    return s;
  }
}
