import { describe, expect, it, vi, beforeEach } from "vitest";
import { RecordingTracer } from "@repo/core-testing/instrumentation";
import { ArticlesRepository } from "@/infrastructure/repositories/articles.repository";
import { articlesRepositoryContract } from "@/__contracts__/articles-repository.contract";
import { stubPayloadConfig } from "@repo/core-testing/payload/stub-config";

// ---------------------------------------------------------------------------
// In-memory Payload stub used by both the contract suite and impl-specific tests
// ---------------------------------------------------------------------------

function buildPayloadStub() {
  const store = new Map<string, Record<string, unknown>>();

  return {
    create: vi.fn(
      async ({
        data,
      }: {
        collection: string;
        data: Record<string, unknown>;
        overrideAccess?: boolean;
      }) => {
        // Payload assigns an id; here we require the data to carry one
        // (the repository passes `id` via the mapped domain object implicitly
        // through the Article — we expose the mapped doc back from createArticle).
        // The stub returns the data as-is so the mapDoc function can work.
        const doc = { id: `stub-${store.size + 1}`, ...data };
        store.set(String(doc.id), doc);
        return doc;
      },
    ),
    find: vi.fn(
      async ({
        where,
        limit,
      }: {
        collection: string;
        where?: {
          slug?: { equals: string };
          status?: { equals: string };
          author?: { equals: string };
        };
        limit?: number;
        page?: number;
        overrideAccess?: boolean;
      }) => {
        let docs = Array.from(store.values());
        if (where?.slug) {
          docs = docs.filter((d) => d.slug === where.slug?.equals);
        }
        if (where?.status) {
          docs = docs.filter((d) => d.status === where.status?.equals);
        }
        if (where?.author) {
          docs = docs.filter((d) => d.author === where.author?.equals);
        }
        if (limit !== undefined) {
          docs = docs.slice(0, limit);
        }
        return { docs };
      },
    ),
    findByID: vi.fn(
      async ({ id }: { collection: string; id: string; overrideAccess?: boolean }) => {
        const doc = store.get(String(id));
        if (!doc) {
          const err = Object.assign(new Error(`Not found: ${id}`), { status: 404 });
          throw err;
        }
        return doc;
      },
    ),
    update: vi.fn(
      async ({
        id,
        data,
      }: {
        collection: string;
        id: string;
        data: Record<string, unknown>;
        overrideAccess?: boolean;
      }) => {
        const existing = store.get(String(id));
        if (!existing) {
          const err = Object.assign(new Error(`Not found: ${id}`), { status: 404 });
          throw err;
        }
        const updated = { ...existing, ...data };
        store.set(String(id), updated);
        return updated;
      },
    ),
  };
}

vi.mock("payload", () => ({
  getPayload: vi.fn(),
}));

// ---------------------------------------------------------------------------
// Contract suite
// ---------------------------------------------------------------------------

describe("ArticlesRepository", () => {
  describe("contract", () => {
    const tracer = new RecordingTracer();
    articlesRepositoryContract.run(
      async () => {
        const stub = buildPayloadStub();
        const { getPayload } = await import("payload");
        (getPayload as ReturnType<typeof vi.fn>).mockResolvedValue(stub);
        return new ArticlesRepository(stubPayloadConfig, tracer);
      },
      { tracer: () => tracer },
    );
  });

  // -------------------------------------------------------------------------
  // Impl-specific tests: Payload doc → domain mapping
  // -------------------------------------------------------------------------

  describe("Payload doc → domain mapping", () => {
    beforeEach(() => {
      vi.clearAllMocks();
    });

    it("maps a Payload doc to a domain Article on getArticleBySlug", async () => {
      const { getPayload } = await import("payload");
      const findMock = vi.fn().mockResolvedValue({
        docs: [
          {
            id: "p-123",
            title: "Hello",
            slug: "hello",
            content: { type: "doc", children: [] },
            status: "published",
            author: "u1",
            createdAt: "2026-05-04T12:00:00.000Z",
            updatedAt: "2026-05-04T12:00:00.000Z",
          },
        ],
      });
      (getPayload as ReturnType<typeof vi.fn>).mockResolvedValue({
        find: findMock,
      });

      const repo = new ArticlesRepository(stubPayloadConfig);
      const result = await repo.getArticleBySlug("hello");

      expect(findMock).toHaveBeenCalledWith({
        collection: "articles",
        where: { slug: { equals: "hello" } },
        limit: 1,
        overrideAccess: true,
      });
      expect(result?.id).toBe("p-123");
      expect(result?.slug).toBe("hello");
      expect(result?.status).toBe("published");
      expect(result?.authorId).toBe("u1");
      expect(result?.createdAt).toBeInstanceOf(Date);
    });

    it("returns undefined when slug is not found", async () => {
      const { getPayload } = await import("payload");
      (getPayload as ReturnType<typeof vi.fn>).mockResolvedValue({
        find: vi.fn().mockResolvedValue({ docs: [] }),
      });

      const repo = new ArticlesRepository(stubPayloadConfig);
      const result = await repo.getArticleBySlug("missing");
      expect(result).toBeUndefined();
    });
  });
});
