import type { QueryContext } from "@tailor-platform/erp-kit/core";
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import { baseQueuedAudit, baseSentAudit } from "../testing/fixtures";
import { run, type ListAuditBySourceInput } from "./listAuditBySource";

describe("listAuditBySource", () => {
  const baseInput: ListAuditBySourceInput = {
    sourceType: "PURCHASE_ORDER",
    sourceId: "po-1",
  };

  const recipientCtx: QueryContext = {
    actorId: "user-1",
    permissions: [],
  };

  it("returns audit rows for every Notification fanned out from the supplied source", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults(
      [{ id: "notification-1" }, { id: "notification-2" }],
      [
        { ...baseQueuedAudit, notificationId: "notification-1" },
        { ...baseSentAudit, notificationId: "notification-2" },
      ],
    );

    const result = await run(mock.db, baseInput, recipientCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(2);
    }
  });

  it("returns rows ordered by occurredAt ascending", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([{ id: "notification-1" }], [{ ...baseQueuedAudit }, { ...baseSentAudit }]);

    const result = await run(mock.db, baseInput, recipientCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      // The mock does not sort, but the call asks for orderBy=occurredAt asc.
      expect(result.value.items).toHaveLength(2);
    }
  });

  it("returns an empty list when the source has no Notifications", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([]);

    const result = await run(mock.db, baseInput, recipientCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toEqual([]);
    }
  });

  it("honors limit/offset pagination and reports hasNextPage when more rows remain", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    // 1. parent notifications, 2. audit rows (limit + 1 -> hasNextPage = true)
    mock.enqueueResults([{ id: "notification-1" }], [{ ...baseQueuedAudit }, { ...baseSentAudit }]);

    const result = await run(mock.db, { ...baseInput, limit: 1, offset: 0 }, recipientCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
      expect(result.value.hasNextPage).toBe(true);
    }
  });

  it("restricts the caller to audit rows of their own notifications", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    // The parent resolution carries the recipientUserId = caller filter, so
    // only the caller-owned notification id comes back.
    mock.enqueueResults(
      [{ id: "notification-own" }],
      [{ ...baseQueuedAudit, notificationId: "notification-own" }],
    );

    const result = await run(mock.db, baseInput, recipientCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
    }
    // The parent select is scoped by recipientUserId = caller.
    expect(mock.selects[0]?.sql).toContain("recipientUserId");
    expect(mock.selects[0]?.parameters).toContain("user-1");
  });

  it("returns an empty list when the fan-out includes no notification addressed to the caller", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    // The recipient-scoped parent resolution matches nothing.
    mock.enqueueResults([]);

    const result = await run(mock.db, baseInput, recipientCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toEqual([]);
    }
    // The audit table was never queried.
    expect(mock.selects).toHaveLength(1);
    expect(mock.selects[0]?.parameters).toContain("user-1");
  });

  it("returns anonymized rows with errorDetail and user-driven occurredBy cleared while structural fields are preserved", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults(
      [{ id: "notification-1" }],
      [
        {
          ...baseQueuedAudit,
          occurredBy: "TOMBSTONED",
          errorDetail: null,
        },
      ],
    );

    const result = await run(mock.db, baseInput, recipientCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      const row = result.value.items[0];
      expect(row?.occurredBy).toBe("TOMBSTONED");
      expect(row?.errorDetail).toBeNull();
      expect(row?.id).toBeDefined();
      expect(row?.notificationId).toBeDefined();
      expect(row?.eventType).toBeDefined();
    }
  });
});
