import { getPayload as _getPayload } from "payload";
import type { SanitizedConfig } from "payload";
import { randomUUID } from "node:crypto";
import { createHash } from "node:crypto";
import type { AuditLogProtocol } from "@repo/core-shared/di";
import type { IDataDelete } from "./data-delete.interface";
import type {
  DeletionMode,
  DeletionCertificate,
  DeletionAffected,
} from "./dsr-types";
import type { DsrCollectionCustom } from "./dsr-collection-custom";

type PayloadDoc = Record<string, unknown>;

type PayloadAPI = {
  find(args: {
    collection: string;
    where: Record<string, unknown>;
    overrideAccess: true;
    limit: number;
  }): Promise<{ docs: PayloadDoc[] }>;
  update(args: {
    collection: string;
    id: string;
    data: Record<string, unknown>;
    overrideAccess: true;
  }): Promise<PayloadDoc>;
  delete(args: {
    collection: string;
    id: string;
    overrideAccess: true;
  }): Promise<PayloadDoc>;
};

type GetPayload = (args: { config: SanitizedConfig }) => Promise<PayloadAPI>;

function buildWhere(field: string, subjectId: string): Record<string, unknown> {
  return field === "id"
    ? { id: { equals: subjectId } }
    : { [field]: { equals: subjectId } };
}

async function softRedactOwnerRows(
  payload: PayloadAPI,
  slug: string,
  docs: PayloadDoc[],
  exportableFields: string[],
  extraData: Record<string, unknown> = {},
): Promise<void> {
  const hasContent =
    exportableFields.length > 0 || Object.keys(extraData).length > 0;
  if (!hasContent) return;
  const nullData = Object.fromEntries(exportableFields.map((f) => [f, null]));
  const data = { ...nullData, ...extraData };
  for (const doc of docs) {
    await payload.update({
      collection: slug,
      id: String(doc["id"]),
      data,
      overrideAccess: true,
    });
  }
}

async function cascadeHardDeleteOwnerRows(
  payload: PayloadAPI,
  slug: string,
  docs: PayloadDoc[],
): Promise<void> {
  for (const doc of docs) {
    await payload.delete({
      collection: slug,
      id: String(doc["id"]),
      overrideAccess: true,
    });
  }
}

async function redactReferenceField(
  payload: PayloadAPI,
  slug: string,
  field: string,
  docs: PayloadDoc[],
): Promise<void> {
  for (const doc of docs) {
    await payload.update({
      collection: slug,
      id: String(doc["id"]),
      data: { [field]: null },
      overrideAccess: true,
    });
  }
}

/**
 * Payload-backed IDataDelete. Implements both GDPR Art. 17 deletion modes:
 *
 * - "soft" — restricts processing, NULLs exportable PII, redacts reference
 *   links. Row structure preserved (other subjects' data in shared rows intact).
 * - "cascade-hard" — hard-deletes self/owner rows and redacts reference fields.
 *   Admin-only; auth enforcement at the tRPC procedure layer.
 *
 * Emits RESTRICT or DELETE audit entries per affected collection.
 */
export class PayloadDataDelete implements IDataDelete {
  constructor(
    private readonly config: SanitizedConfig,
    private readonly auditLog: AuditLogProtocol,
    private readonly getPayloadFn: GetPayload = _getPayload as unknown as GetPayload,
  ) {}

  async deleteSubjectData(
    subjectId: string,
    mode: DeletionMode,
  ): Promise<DeletionCertificate> {
    const payload = await this.getPayloadFn({ config: this.config });
    const correlationId = randomUUID();
    const timestamp = new Date().toISOString();
    const affected: DeletionAffected[] = [];

    for (const collection of this.config.collections) {
      const custom = (collection.custom ?? {}) as DsrCollectionCustom;
      if (!custom.subject) continue;

      const { field, kind } = custom.subject;
      const result = await payload.find({
        collection: collection.slug,
        where: buildWhere(field, subjectId),
        overrideAccess: true,
        limit: 1000,
      });
      if (result.docs.length === 0) continue;

      if (kind === "self" || kind === "owner") {
        await this.processOwnerRows(
          payload,
          collection.slug,
          mode,
          custom,
          result.docs,
          subjectId,
          correlationId,
          affected,
        );
      } else {
        await this.processReferenceRows(
          payload,
          collection.slug,
          field,
          mode,
          result.docs,
          subjectId,
          correlationId,
          affected,
        );
      }
    }

    return this.buildCertificate(
      subjectId,
      mode,
      timestamp,
      correlationId,
      affected,
    );
  }

  private async processOwnerRows(
    payload: PayloadAPI,
    slug: string,
    mode: DeletionMode,
    custom: DsrCollectionCustom,
    docs: PayloadDoc[],
    subjectId: string,
    correlationId: string,
    affected: DeletionAffected[],
  ): Promise<void> {
    const piiMeta = custom.pii ?? {};
    const exportableFields = Object.entries(piiMeta)
      .filter(([, m]) => m.exportable)
      .map(([name]) => name);

    if (mode === "soft") {
      const kind = custom.subject?.kind;
      const extraData: Record<string, unknown> =
        kind === "self"
          ? { processingRestrictedAt: new Date().toISOString() }
          : {};
      await softRedactOwnerRows(
        payload,
        slug,
        docs,
        exportableFields,
        extraData,
      );
      affected.push({
        collection: slug,
        rowsAffected: docs.length,
        action: "redacted",
        fields: exportableFields,
      });
      await this.recordAudit(
        subjectId,
        "RESTRICT",
        slug,
        exportableFields,
        correlationId,
        "art-17-request",
      );
    } else {
      await cascadeHardDeleteOwnerRows(payload, slug, docs);
      affected.push({
        collection: slug,
        rowsAffected: docs.length,
        action: "deleted",
      });
      await this.recordAudit(
        subjectId,
        "DELETE",
        slug,
        [],
        correlationId,
        "art-17-request",
      );
    }
  }

  private async processReferenceRows(
    payload: PayloadAPI,
    slug: string,
    field: string,
    mode: DeletionMode,
    docs: PayloadDoc[],
    subjectId: string,
    correlationId: string,
    affected: DeletionAffected[],
  ): Promise<void> {
    await redactReferenceField(payload, slug, field, docs);
    affected.push({
      collection: slug,
      rowsAffected: docs.length,
      action: "redacted",
      fields: [field],
    });
    const action = mode === "soft" ? "RESTRICT" : "DELETE";
    await this.recordAudit(
      subjectId,
      action,
      slug,
      [field],
      correlationId,
      "art-17-request",
    );
  }

  private async recordAudit(
    subjectId: string,
    action: "RESTRICT" | "DELETE" | "UNRESTRICT",
    resourceType: string,
    changedFields: string[],
    correlationId: string,
    reason: string,
  ): Promise<void> {
    await this.auditLog.record({
      actorId: subjectId,
      actorType: "user",
      actorRoles: [],
      action,
      resource: { type: resourceType },
      changedFields: changedFields.length > 0 ? changedFields : undefined,
      at: new Date(),
      scope: {
        feature: "core-dsr",
        environment: process.env["NODE_ENV"] ?? "development",
        tenant: "default",
      },
      reason,
      correlationId,
      from: { ipTruncated: "system", userAgent: "system" },
      containsPii: false,
      outcome: "success",
    });
  }

  private buildCertificate(
    subjectId: string,
    mode: DeletionMode,
    timestamp: string,
    correlationId: string,
    affected: DeletionAffected[],
  ): DeletionCertificate {
    const certSubjectId =
      mode === "cascade-hard"
        ? `erased-${createHash("sha256").update(subjectId).digest("hex").slice(0, 16)}`
        : subjectId;

    return {
      subjectId: certSubjectId,
      mode,
      timestamp,
      reason: "art-17-request",
      affected,
      auditEntryId: correlationId,
    };
  }
}
