// Local type alias mirroring `AuditEntry` from `@repo/core-shared/audit`.
// Kept inline to avoid a build-graph cycle between core-testing (tooling)
// and core-shared (core). Same pattern used by recording-event-bus,
// recording-realtime-broadcaster, and recording-metrics.

type AuditAction =
  | "VIEW"
  | "CREATE"
  | "UPDATE"
  | "DELETE"
  | "EXPORT"
  | "PERMISSION_CHANGE";

type AuditEntry = {
  actorId: string;
  actorType: "user" | "system" | "service";
  actorRoles: string[];
  action: AuditAction;
  resource: { type: string; id?: string };
  changedFields?: string[];
  at: Date;
  scope: { feature: string; environment: string; tenant: string };
  reason?: string;
  correlationId?: string;
  requestId?: string;
  from: { ipTruncated: string; userAgent: string };
  containsPii: boolean;
  piiCategories?: string[];
  outcome: "success" | "denied" | "error";
  errorCode?: string;
};

/**
 * Test-side recording double for IAuditLog. Mirrors Payload semantics in
 * eraseSubject (pseudonymize rewrites in place; delete filters out) so tests
 * can assert against the same observable state the real impl produces.
 *
 * Use directly via constructor injection in factory-function tests — no
 * container manipulation needed.
 */
export class RecordingAuditLog {
  public recorded: AuditEntry[] = [];
  public erasures: { actorId: string; mode: "pseudonymize" | "delete" }[] = [];

  async record(entry: AuditEntry): Promise<void> {
    // Shallow copy to avoid mutating caller's object during eraseSubject
    this.recorded.push({ ...entry });
  }

  async eraseSubject(actorId: string, mode: "pseudonymize" | "delete"): Promise<void> {
    this.erasures.push({ actorId, mode });
    if (mode === "pseudonymize") {
      for (const r of this.recorded) {
        if (r.actorId === actorId) {
          r.actorId = `erased-${actorId}`;
        }
      }
    } else {
      this.recorded = this.recorded.filter((r) => r.actorId !== actorId);
    }
  }

  reset(): void {
    this.recorded = [];
    this.erasures = [];
  }
}
