import { describe, it, expect, vi } from "vitest";
import { MultiSinkAuditLog } from "./multi-sink-audit-log";
import type { AuditEntry } from "@repo/core-shared/audit";
import type { IAuditLog } from "./audit-log.interface";

const sample: AuditEntry = {
  actorId: "user_1",
  actorType: "user",
  actorRoles: [],
  action: "VIEW",
  resource: { type: "articles" },
  at: new Date(),
  scope: { feature: "blog", environment: "test", tenant: "default" },
  from: { ipTruncated: "10.0.0.0", userAgent: "test" },
  containsPii: false,
  outcome: "success",
};

function makeRecorder(): IAuditLog & { records: AuditEntry[]; erasures: string[] } {
  const records: AuditEntry[] = [];
  const erasures: string[] = [];
  return {
    records,
    erasures,
    async record(e) { records.push(e); },
    async eraseSubject(actorId) { erasures.push(actorId); },
  };
}

describe("MultiSinkAuditLog", () => {
  it("record() fans out to every sink", async () => {
    const a = makeRecorder();
    const b = makeRecorder();
    const m = new MultiSinkAuditLog([a, b]);
    await m.record(sample);
    expect(a.records).toHaveLength(1);
    expect(b.records).toHaveLength(1);
  });

  it("settle-all: one sink failing does not skip others", async () => {
    const errSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
    const a: IAuditLog = { record: async () => { throw new Error("a-fail"); }, eraseSubject: async () => {} };
    const b = makeRecorder();
    const m = new MultiSinkAuditLog([a, b]);

    await m.record(sample);

    expect(b.records).toHaveLength(1);  // b still received the entry
    expect(errSpy).toHaveBeenCalledOnce();
    const written = errSpy.mock.calls[0]![0] as string;
    const parsed = JSON.parse(written.trimEnd());
    expect(parsed._type).toBe("audit-sink-error");
    expect(parsed.error).toContain("a-fail");
    errSpy.mockRestore();
  });

  it("eraseSubject() fans out to every sink", async () => {
    const a = makeRecorder();
    const b = makeRecorder();
    const m = new MultiSinkAuditLog([a, b]);
    await m.eraseSubject("user_1", "delete");
    expect(a.erasures).toEqual(["user_1"]);
    expect(b.erasures).toEqual(["user_1"]);
  });
});
