import { getPayload as _getPayload } from "payload";
import type { SanitizedConfig } from "payload";
import type { AuditLogProtocol } from "@repo/core-shared/di";
import type { IDataExport } from "./data-export.interface";
import type {
  DsrFormat,
  UserDataBundle,
  CollectionDataBucket,
  SubjectReference,
} from "./dsr-types";
import type { DsrCollectionCustom } from "./dsr-collection-custom";

// Mirrors packages/core-dsr/src/contexts/user-data.jsonld — update both together.
const USER_DATA_JSONLD_CONTEXT: Record<string, unknown> = {
  "@vocab": "https://schema.org/",
  dsr: "https://w3.org/ns/dpv#",
  prov: "https://www.w3.org/ns/prov#",
  subjectId: "identifier",
  exportedAt: "dateCreated",
  format: "encodingFormat",
  data: { "@id": "prov:hadMember", "@container": "@index" },
  asSelf: { "@id": "dsr:hasPersonalDataHandling", "@type": "@id" },
  asReference: { "@id": "dsr:hasDataSubjectRight", "@type": "@id" },
  rowId: "identifier",
  linkedField: "name",
  linkedThrough: { "@id": "isPartOf", "@type": "@id" },
  auditLog: { "@id": "prov:wasGeneratedBy", "@type": "@id" },
  UserDataBundle: "dsr:RightOfAccess",
  SubjectReference: "dsr:DataSubjectRight",
};

type PayloadDoc = Record<string, unknown>;

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

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

/**
 * Payload-backed IDataExport. Walks all collections annotated with
 * `custom.subject` linkage, segments rows by role (self/owner vs reference),
 * and filters to fields marked `exportable: true` in `custom.pii`.
 *
 * Emits an EXPORT audit entry for every call. The `getPayloadFn` parameter is
 * injectable for unit tests; production code omits it.
 */
export class PayloadDataExport implements IDataExport {
  constructor(
    private readonly config: SanitizedConfig,
    private readonly auditLog: AuditLogProtocol,
    private readonly getPayloadFn: GetPayload = _getPayload as unknown as GetPayload,
  ) {}

  async exportSubjectData(
    subjectId: string,
    format: DsrFormat,
  ): Promise<UserDataBundle> {
    const payload = await this.getPayloadFn({ config: this.config });
    const data: Record<string, CollectionDataBucket> = {};

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

      const { field, kind } = custom.subject;
      const where =
        field === "id"
          ? { id: { equals: subjectId } }
          : { [field]: { equals: subjectId } };

      const result = await payload.find({
        collection: collection.slug,
        where,
        overrideAccess: true,
        limit: 1000,
      });

      if (result.docs.length === 0) continue;

      if (kind === "self" || kind === "owner") {
        const piiMeta = custom.pii ?? {};
        const exportableFields = Object.entries(piiMeta)
          .filter(([, m]) => m.exportable)
          .map(([name]) => name);

        data[collection.slug] = {
          asSelf: result.docs.map((doc) => {
            const row: PayloadDoc = { id: doc["id"] };
            for (const f of exportableFields) {
              if (f in doc) row[f] = doc[f];
            }
            return row;
          }),
        };
      } else {
        // reference kind — expose only linking coordinates, not row content
        data[collection.slug] = {
          asReference: result.docs.map(
            (doc): SubjectReference => ({
              rowId: String(doc["id"]),
              linkedField: field,
              linkedThrough: collection.slug,
            }),
          ),
        };
      }
    }

    await this.auditLog.record({
      actorId: subjectId,
      actorType: "user",
      actorRoles: [],
      action: "EXPORT",
      resource: { type: "subject-data" },
      at: new Date(),
      scope: {
        feature: "core-dsr",
        environment: process.env["NODE_ENV"] ?? "development",
        tenant: "default",
      },
      from: { ipTruncated: "system", userAgent: "system" },
      containsPii: false,
      outcome: "success",
    });

    const bundle: UserDataBundle = {
      subjectId,
      exportedAt: new Date().toISOString(),
      format,
      data,
    };

    if (format === "json-ld") {
      bundle["@context"] = USER_DATA_JSONLD_CONTEXT;
    }

    return bundle;
  }
}
