import { it, expect, beforeEach, describe } from "vitest";
import { defineContractSuite } from "@repo/core-testing/contract";
import type { IArticlesRepository } from "../application/repositories/articles.repository.interface.js";
import { articleFactory } from "../__factories__/article.factory.js";

export const articlesRepositoryContract =
  defineContractSuite<IArticlesRepository>(
    "IArticlesRepository",
    ({ buildSubject, getTracer }) => {
      let repo: IArticlesRepository;

      beforeEach(async () => {
        articleFactory.reset();
        repo = await buildSubject();
      });

      // --- createArticle ---

      it("createArticle returns an article with an id and the correct fields", async () => {
        const seed = articleFactory.build({ title: "Hello World" });
        const created = await repo.createArticle(seed);
        // Implementations may assign their own id (e.g. Payload), so we only
        // verify the id is a non-empty string and the other fields match.
        expect(typeof created.id).toBe("string");
        expect(created.id.length).toBeGreaterThan(0);
        expect(created.title).toBe("Hello World");
        expect(created.slug).toBe(seed.slug);
        expect(created.status).toBe(seed.status);
        expect(created.authorId).toBe(seed.authorId);
      });

      // --- getArticle ---

      it("createArticle then getArticle returns it by the returned id", async () => {
        const seed = articleFactory.build();
        const created = await repo.createArticle(seed);
        // Use the id returned by createArticle (Payload may differ from seed.id)
        const result = await repo.getArticle(created.id);
        expect(result).toBeDefined();
        expect(result?.id).toBe(created.id);
        expect(result?.slug).toBe(seed.slug);
      });

      it("getArticle returns undefined for missing id", async () => {
        expect(await repo.getArticle("does-not-exist")).toBeUndefined();
      });

      // --- getArticleBySlug ---

      it("createArticle then getArticleBySlug returns it by slug", async () => {
        const seed = articleFactory.build({ slug: "my-slug" });
        const created = await repo.createArticle(seed);
        const result = await repo.getArticleBySlug("my-slug");
        expect(result).toBeDefined();
        expect(result?.id).toBe(created.id);
        expect(result?.slug).toBe("my-slug");
      });

      it("getArticleBySlug returns undefined for missing slug", async () => {
        expect(await repo.getArticleBySlug("does-not-exist")).toBeUndefined();
      });

      // --- getArticles ---

      it("getArticles returns empty array when no articles", async () => {
        const list = await repo.getArticles();
        expect(list).toHaveLength(0);
      });

      it("getArticles returns all articles when no filter", async () => {
        await repo.createArticle(articleFactory.build());
        await repo.createArticle(articleFactory.build());
        const list = await repo.getArticles();
        expect(list).toHaveLength(2);
      });

      it("getArticles filters by status", async () => {
        await repo.createArticle(articleFactory.build({ status: "draft" }));
        await repo.createArticle(
          articleFactory.build({ status: "published" }),
        );
        const drafts = await repo.getArticles({ status: "draft" });
        expect(drafts).toHaveLength(1);
        expect(drafts[0]?.status).toBe("draft");
      });

      it("getArticles filters by authorId", async () => {
        await repo.createArticle(
          articleFactory.build({ authorId: "author-a" }),
        );
        await repo.createArticle(
          articleFactory.build({ authorId: "author-b" }),
        );
        const result = await repo.getArticles({ authorId: "author-a" });
        expect(result).toHaveLength(1);
        expect(result[0]?.authorId).toBe("author-a");
      });

      // --- updateArticle ---

      it("updateArticle changes fields and returns updated article", async () => {
        const seed = articleFactory.build({ status: "draft" });
        const created = await repo.createArticle(seed);
        // Use the returned id for the update lookup
        const updated = await repo.updateArticle(created.id, {
          status: "published",
        });
        expect(updated).toBeDefined();
        expect(updated?.id).toBe(created.id);
        expect(updated?.status).toBe("published");
      });

      it("updateArticle returns undefined for missing id", async () => {
        const result = await repo.updateArticle("no-such-id", {
          title: "new",
        });
        expect(result).toBeUndefined();
      });

      describe("span emission (R50)", () => {
        it("getArticles emits articles.getArticles span with op=repository", async () => {
          if (!getTracer) return;
          const tracer = getTracer();
          tracer.reset();
          await repo.getArticles({ limit: 5 });
          const span = tracer.findSpan("articles.getArticles");
          expect(span).toBeDefined();
          expect(span!.op).toBe("repository");
          expect(span!.attributes.limit).toBe(5);
        });

        it("getArticle emits articles.getArticle span with id attribute", async () => {
          if (!getTracer) return;
          const tracer = getTracer();
          tracer.reset();
          await repo.getArticle("nonexistent");
          const span = tracer.findSpan("articles.getArticle");
          expect(span).toBeDefined();
          expect(span!.op).toBe("repository");
          expect(span!.attributes.id).toBe("nonexistent");
        });

        it("getArticleBySlug emits articles.getArticleBySlug span with slug attribute", async () => {
          if (!getTracer) return;
          const tracer = getTracer();
          tracer.reset();
          await repo.getArticleBySlug("nonexistent");
          const span = tracer.findSpan("articles.getArticleBySlug");
          expect(span).toBeDefined();
          expect(span!.op).toBe("repository");
          expect(span!.attributes.slug).toBe("nonexistent");
        });

        it("createArticle emits articles.createArticle span", async () => {
          if (!getTracer) return;
          const tracer = getTracer();
          tracer.reset();
          const seed = articleFactory.build();
          await repo.createArticle(seed);
          const span = tracer.findSpan("articles.createArticle");
          expect(span).toBeDefined();
          expect(span!.op).toBe("repository");
          expect(span!.attributes.slug).toBe(seed.slug);
        });

        it("updateArticle emits articles.updateArticle span", async () => {
          if (!getTracer) return;
          const tracer = getTracer();
          tracer.reset();
          const seed = articleFactory.build();
          const created = await repo.createArticle(seed);
          await repo.updateArticle(created.id, { title: "Updated" });
          const span = tracer.findSpan("articles.updateArticle");
          expect(span).toBeDefined();
          expect(span!.op).toBe("repository");
          expect(span!.attributes.id).toBe(created.id);
        });
      });
    },
  );
