import { describe, it, expect, vi, beforeEach } from "vitest";

vi.mock("@paralleldrive/cuid2", () => ({
  createId: vi.fn().mockReturnValue("test-event-id"),
}));

vi.mock("./outbox.repository.js", () => ({
  outboxRepository: {
    create: vi.fn(),
  },
}));

import { OutboxService } from "./outbox.service.js";
import { outboxRepository } from "./outbox.repository.js";
import { getTopicForEvent } from "./events.js";

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

  beforeEach(() => {
    vi.clearAllMocks();
    service = new OutboxService();
  });

  describe("publish", () => {
    it("should call outboxRepository.create with correct payload structure", async () => {
      const data = { title: "Test Chapter" };
      const eventId = await service.publish(
        "chapter.created",
        "Chapter",
        "ch-1",
        data,
      );

      expect(outboxRepository.create).toHaveBeenCalledTimes(1);
      const [message] = vi.mocked(outboxRepository.create).mock.calls[0]!;
      expect(message.id).toBe("test-event-id");
      expect(message.aggregateType).toBe("Chapter");
      expect(message.aggregateId).toBe("ch-1");
      expect(message.eventType).toBe("chapter.created");
      expect(message.status).toBe("pending");
      expect(typeof message.payload).toBe("string");
      expect(eventId).toBe("test-event-id");
    });

    it("should set status to pending", async () => {
      await service.publish("chapter.updated", "Chapter", "ch-1", {});
      const [message] = vi.mocked(outboxRepository.create).mock.calls[0]!;
      expect(message.status).toBe("pending");
    });

    it("should set topic based on event type via getTopicForEvent", async () => {
      await service.publish("chapter.created", "Chapter", "ch-1", {});
      const [message] = vi.mocked(outboxRepository.create).mock.calls[0]!;
      expect(message.topic).toBe(getTopicForEvent("chapter.created"));
    });

    it("should serialize payload as JSON string containing event data", async () => {
      const data = { title: "Test Chapter", order: 2 };
      await service.publish("chapter.created", "Chapter", "ch-1", data);
      const [message] = vi.mocked(outboxRepository.create).mock.calls[0]!;

      const parsed = JSON.parse(message.payload);
      expect(parsed.aggregate_id).toBe("ch-1");
      expect(parsed.event_type).toBe("edu.content.chapter.created");
      expect(parsed.action).toBe("created");
      expect(parsed.title).toBe("Test Chapter");
      expect(parsed.order).toBe(2);
    });

    it("should return the generated eventId", async () => {
      const eventId = await service.publish(
        "chapter.deleted",
        "Chapter",
        "ch-1",
        {},
      );
      expect(eventId).toBe("test-event-id");
    });

    it("should pass tx to repository when provided", async () => {
      const tx = {} as never;
      await service.publish("chapter.created", "Chapter", "ch-1", {}, tx);
      const [, passedTx] = vi.mocked(outboxRepository.create).mock.calls[0]!;
      expect(passedTx).toBe(tx);
    });

    it("should pass undefined tx when not provided", async () => {
      await service.publish("chapter.created", "Chapter", "ch-1", {});
      const [, passedTx] = vi.mocked(outboxRepository.create).mock.calls[0]!;
      expect(passedTx).toBeUndefined();
    });

    it("should include aggregate data in payload", async () => {
      const data = { name: "Chapter A", status: "draft" };
      await service.publish("chapter.created", "Chapter", "ch-9", data);
      const [message] = vi.mocked(outboxRepository.create).mock.calls[0]!;
      const parsed = JSON.parse(message.payload);
      expect(parsed.name).toBe("Chapter A");
      expect(parsed.status).toBe("draft");
    });
  });
});
