import type { SanitizedConfig } from "payload";
import type { AuditEntry } from "@repo/core-shared/audit";
import type { IAuditLog } from "./audit-log.interface";
import { pseudonymize } from "./pseudonymize";

type GetPayload = (args: { config: SanitizedConfig }) => Promise<{
  create: (args: { collection: string; data: Record<string, unknown> }) => Promise<unknown>;
  find: (args: {
    collection: string;
    where: Record<string, unknown>;
    limit: number;
    overrideAccess: true;
  }) => Promise<{ docs: Array<{ id: string | number }> }>;
  update: (args: {
    collection: string;
    id: string | number;
    data: Record<string, unknown>;
    overrideAccess: true;
  }) => Promise<unknown>;
  delete: (args: {
    collection: string;
    where: Record<string, unknown>;
    overrideAccess: true;
  }) => Promise<unknown>;
}>;

/**
 * Local-cache audit sink: writes entries to the `audit-logs` Payload
 * collection. The collection is append-only by access-rule
 * (`update: () => false`); the eraseSubject path uses `overrideAccess: true`
 * to bypass for the privileged GDPR pseudonymization op.
 *
 * The getPayload param is injectable for tests; production callers pass
 * the real `getPayload` from `payload`.
 */
export class PayloadAuditLog implements IAuditLog {
  constructor(
    private readonly config: SanitizedConfig,
    private readonly getPayload: GetPayload,
  ) {}

  async record(entry: AuditEntry): Promise<void> {
    const payload = await this.getPayload({ config: this.config });
    await payload.create({
      collection: "audit-logs",
      data: {
        actorId: entry.actorId,
        actorType: entry.actorType,
        actorRoles: entry.actorRoles,
        action: entry.action,
        resourceType: entry.resource.type,
        resourceId: entry.resource.id ?? null,
        changedFields: entry.changedFields ?? null,
        scopeFeature: entry.scope.feature,
        scopeEnvironment: entry.scope.environment,
        scopeTenant: entry.scope.tenant,
        reason: entry.reason ?? null,
        correlationId: entry.correlationId ?? null,
        requestId: entry.requestId ?? null,
        ipTruncated: entry.from.ipTruncated,
        userAgent: entry.from.userAgent,
        containsPii: entry.containsPii,
        piiCategories: entry.piiCategories ?? null,
        outcome: entry.outcome,
        errorCode: entry.errorCode ?? null,
      },
    });
  }

  async eraseSubject(actorId: string, mode: "pseudonymize" | "delete"): Promise<void> {
    const payload = await this.getPayload({ config: this.config });

    if (mode === "delete") {
      await payload.delete({
        collection: "audit-logs",
        where: { actorId: { equals: actorId } },
        overrideAccess: true,
      });
      return;
    }

    // mode === "pseudonymize"
    // Fetch all matching docs. Limit is 10_000 — a subject with more than
    // 10k audit entries will not have all entries pseudonymized in one call.
    // This is an accepted v1 limitation; callers may loop if needed.
    const { docs } = await payload.find({
      collection: "audit-logs",
      where: { actorId: { equals: actorId } },
      limit: 10_000,
      overrideAccess: true,
    });

    const pseudonym = pseudonymize(actorId);
    for (const doc of docs) {
      await payload.update({
        collection: "audit-logs",
        id: doc.id,
        data: { actorId: pseudonym },
        overrideAccess: true,
      });
    }
  }
}
