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

vi.mock("./questions.repository.js", () => ({
  questionsRepository: {
    findById: vi.fn(),
    findByKnowledgePointId: vi.fn(),
    find: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
  },
}));

import { questionsRepository } from "./questions.repository.js";

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

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

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

  describe("createQuestion", () => {
    it("should create a question and publish event", async () => {
      const input = {
        knowledgePointId: "kp-1",
        type: "single_choice",
        content: "What is 2+2?",
        answer: "4",
        createdBy: "user-1",
      };
      const result = await service.createQuestion(input);

      expect(result.id).toBeDefined();
      expect(questionsRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          knowledgePointId: "kp-1",
          type: "single_choice",
          content: "What is 2+2?",
          answer: "4",
          status: "draft",
          source: "manual",
          createdBy: "user-1",
        }),
      );
      expect(mockOutbox.publish).toHaveBeenCalledWith(
        "question.created",
        "Question",
        result.id,
        expect.objectContaining({
          knowledge_point_id: "kp-1",
          type: "single_choice",
          created_by: "user-1",
        }),
      );
    });

    it("should default difficulty to 3 and source to manual", async () => {
      await service.createQuestion({
        knowledgePointId: "kp-1",
        type: "essay",
        content: "Write an essay",
        answer: "Sample answer",
        createdBy: "user-1",
      });
      expect(questionsRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          difficulty: 3,
          source: "manual",
        }),
      );
    });
  });

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

  describe("updateQuestion", () => {
    it("should publish published event when status is published", async () => {
      vi.mocked(questionsRepository.findById).mockResolvedValue({
        id: "q-1",
        knowledgePointId: "kp-1",
        type: "single_choice",
        content: "Q",
        options: null,
        answer: "A",
        explanation: null,
        difficulty: 3,
        status: "draft",
        source: "manual",
        createdBy: "u",
        metadata: null,
        createdAt: new Date(),
        updatedAt: new Date(),
      });

      await service.updateQuestion("q-1", { status: "published" });

      expect(mockOutbox.publish).toHaveBeenCalledWith(
        "question.published",
        "Question",
        "q-1",
        expect.any(Object),
      );
    });

    it("should publish updated event for non-status updates", async () => {
      vi.mocked(questionsRepository.findById).mockResolvedValue({
        id: "q-1",
        knowledgePointId: "kp-1",
        type: "single_choice",
        content: "Q",
        options: null,
        answer: "A",
        explanation: null,
        difficulty: 3,
        status: "draft",
        source: "manual",
        createdBy: "u",
        metadata: null,
        createdAt: new Date(),
        updatedAt: new Date(),
      });

      await service.updateQuestion("q-1", { content: "New content" });

      expect(mockOutbox.publish).toHaveBeenCalledWith(
        "question.updated",
        "Question",
        "q-1",
        expect.any(Object),
      );
    });
  });

  describe("deleteQuestion", () => {
    it("should delete and publish deleted event", async () => {
      vi.mocked(questionsRepository.findById).mockResolvedValue({
        id: "q-1",
        knowledgePointId: "kp-1",
        type: "single_choice",
        content: "Q",
        options: null,
        answer: "A",
        explanation: null,
        difficulty: 3,
        status: "draft",
        source: "manual",
        createdBy: "u",
        metadata: null,
        createdAt: new Date(),
        updatedAt: new Date(),
      });

      await service.deleteQuestion("q-1");

      expect(questionsRepository.delete).toHaveBeenCalledWith("q-1");
      expect(mockOutbox.publish).toHaveBeenCalledWith(
        "question.deleted",
        "Question",
        "q-1",
        { deleted: true },
      );
    });
  });
});
