import { describe, it, expect, vi, beforeEach } from "vitest";
import { ChaptersService } from "./chapters.service.js";
import { NotFoundError } from "../shared/errors/application-error.js";

vi.mock("./chapters.repository.js", () => ({
  chaptersRepository: {
    findById: vi.fn(),
    findByTextbookId: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
  },
}));

import { chaptersRepository } from "./chapters.repository.js";

const mockOutbox = {
  publish: vi.fn().mockResolvedValue("event-id"),
};

describe("ChaptersService", () => {
  let service: ChaptersService;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new ChaptersService(mockOutbox as never);
  });

  describe("createChapter", () => {
    it("should create a chapter and publish event", async () => {
      const input = {
        textbookId: "tb-1",
        title: "Chapter 1",
        order: 5,
      };
      const result = await service.createChapter(input);

      expect(result.id).toBeDefined();
      expect(chaptersRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          textbookId: "tb-1",
          title: "Chapter 1",
          order: 5,
          status: "draft",
        }),
      );
      expect(mockOutbox.publish).toHaveBeenCalledWith(
        "chapter.created",
        "Chapter",
        result.id,
        expect.objectContaining({
          textbook_id: "tb-1",
          title: "Chapter 1",
          order: 5,
        }),
      );
    });

    it("should default order to 0 when not provided", async () => {
      await service.createChapter({
        textbookId: "tb-1",
        title: "Ch",
      });
      expect(chaptersRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({ order: 0 }),
      );
    });
  });

  describe("getChapter", () => {
    it("should return chapter when found", async () => {
      const mockChapter = {
        id: "ch-1",
        textbookId: "tb-1",
        title: "Ch",
        order: 0,
        parentId: null,
        status: "draft",
        createdAt: new Date(),
        updatedAt: new Date(),
      };
      vi.mocked(chaptersRepository.findById).mockResolvedValue(mockChapter);
      const result = await service.getChapter("ch-1");
      expect(result).toBe(mockChapter);
    });

    it("should throw NotFoundError when not found", async () => {
      vi.mocked(chaptersRepository.findById).mockResolvedValue(undefined);
      await expect(service.getChapter("nope")).rejects.toThrow(NotFoundError);
    });
  });

  describe("updateChapter", () => {
    it("should update and publish event", async () => {
      const existing = {
        id: "ch-1",
        textbookId: "tb-1",
        title: "Old",
        order: 0,
        parentId: null,
        status: "draft",
        createdAt: new Date(),
        updatedAt: new Date(),
      };
      vi.mocked(chaptersRepository.findById).mockResolvedValue(existing);

      await service.updateChapter("ch-1", { title: "New" });

      expect(chaptersRepository.update).toHaveBeenCalledWith("ch-1", {
        title: "New",
      });
      expect(mockOutbox.publish).toHaveBeenCalledWith(
        "chapter.updated",
        "Chapter",
        "ch-1",
        expect.objectContaining({ title: "New" }),
      );
    });
  });

  describe("deleteChapter", () => {
    it("should delete and publish deleted event", async () => {
      vi.mocked(chaptersRepository.findById).mockResolvedValue({
        id: "ch-1",
        textbookId: "tb-1",
        title: "Ch",
        order: 0,
        parentId: null,
        status: "draft",
        createdAt: new Date(),
        updatedAt: new Date(),
      });

      await service.deleteChapter("ch-1");

      expect(chaptersRepository.delete).toHaveBeenCalledWith("ch-1");
      expect(mockOutbox.publish).toHaveBeenCalledWith(
        "chapter.deleted",
        "Chapter",
        "ch-1",
        { deleted: true },
      );
    });
  });
});
