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 {
  baseFailedAudit,
  baseQueuedAudit,
  baseReadAudit,
  baseSentAudit,
} from "../testing/fixtures";
import { run, type ListAuditByNotificationInput } from "./listAuditByNotification";

describe("listAuditByNotification", () => {
  const baseInput: ListAuditByNotificationInput = { notificationId: "notification-1" };

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

  // The caller-owned parent notification probe result, returned before the
  // audit rows on every self-scoped read.
  const ownedParent = [{ recipientUserId: "user-1" }];

  it("returns every audit row for the supplied notificationId ordered by occurredAt ascending", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults(ownedParent, [
      { ...baseQueuedAudit },
      { ...baseSentAudit },
      { ...baseReadAudit },
    ]);

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

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

  it("honors limit/offset pagination and reports hasNextPage when more rows remain", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    // limit + 1 rows returned -> hasNextPage = true
    mock.enqueueResults(ownedParent, [{ ...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("returns an empty list when the notificationId has no audit rows", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults(ownedParent, []);

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

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

  it("returns the audit rows when the caller is the notification's recipient", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    // 1. parent ownership probe, 2. audit rows
    mock.enqueueResults(ownedParent, [{ ...baseQueuedAudit }, { ...baseReadAudit }]);

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

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

  it("returns an empty list when the caller is not the notification's recipient", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    // The parent notification belongs to another user.
    mock.enqueueResults([{ recipientUserId: "user-2" }]);

    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 — only the ownership probe ran.
    expect(mock.selects).toHaveLength(1);
  });

  it("returns an empty list (not an error) when the caller supplies a missing notificationId", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    // No parent notification — indistinguishable from a not-owned one.
    mock.enqueueResults([]);

    const result = await run(mock.db, { notificationId: "missing" }, recipientCtx);

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

  it("returns rows for system-driven events (QUEUED, SENT, DELIVERED, FAILED) with occurredBy == system", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults(ownedParent, [
      { ...baseQueuedAudit, occurredBy: "system" },
      { ...baseSentAudit, occurredBy: "system" },
      { ...baseFailedAudit, occurredBy: "system" },
    ]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      for (const row of result.value.items) {
        expect(row.occurredBy).toBe("system");
      }
    }
  });

  it("returns rows for user-driven events (SEEN, READ, ARCHIVED) with occurredBy == recipientUserId", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults(ownedParent, [{ ...baseReadAudit, occurredBy: "user-1" }]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items[0]?.occurredBy).toBe("user-1");
    }
  });

  it("returns FAILED rows with populated errorClass and errorDetail", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults(ownedParent, [{ ...baseFailedAudit }]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items[0]?.errorClass).toBe("AdapterTransportError");
      expect(result.value.items[0]?.errorDetail).toBe("SMTP connection refused");
    }
  });

  it("returns anonymized rows with errorDetail cleared and occurredBy = TOMBSTONED while preserving structural fields", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults(ownedParent, [
      {
        ...baseFailedAudit,
        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();
      // Structural fields preserved.
      expect(row?.id).toBeDefined();
      expect(row?.notificationId).toBeDefined();
      expect(row?.eventType).toBeDefined();
      expect(row?.errorClass).toBe("AdapterTransportError");
    }
  });
});
