import { beforeEach, describe, expect, it, vi } from "vitest";
import { NotFoundException } from "@nestjs/common";
import type { Response } from "express";
import { ConfigService } from "@nestjs/config";
import { Test, type TestingModule } from "@nestjs/testing";
import { QueueNotificationsController } from "./queue-notifications.controller";
import { QueueNotificationsService } from "./queue-notifications.service";
import { ApiKeyGuard } from "../common/guards/api-key.guard";

describe("QueueNotificationsController", () => {
  let controller: QueueNotificationsController;

  const mockService = {
    listNotifications: vi.fn(),
    streamNotifications: vi.fn(),
    ackNotification: vi.fn(),
    listTasks: vi.fn(),
  };

  const mockConfigService = {
    get: vi.fn().mockReturnValue("coordinator-api-key"),
  };

  beforeEach(async () => {
    vi.clearAllMocks();

    const module: TestingModule = await Test.createTestingModule({
      controllers: [QueueNotificationsController],
      providers: [
        { provide: QueueNotificationsService, useValue: mockService },
        { provide: ConfigService, useValue: mockConfigService },
      ],
    })
      .overrideGuard(ApiKeyGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get<QueueNotificationsController>(QueueNotificationsController);
  });

  it("returns notification objects", async () => {
    mockService.listNotifications.mockResolvedValue([
      {
        id: "notif-1",
        agent: "mosaic",
        filename: "notif-1.json",
        payload: { type: "task.ready" },
        createdAt: new Date("2026-03-08T22:00:00.000Z"),
      },
    ]);

    await expect(controller.getNotifications()).resolves.toEqual([
      expect.objectContaining({
        id: "notif-1",
        agent: "mosaic",
        filename: "notif-1.json",
        payload: { type: "task.ready" },
      }),
    ]);
  });

  it("streams notifications through the response object", async () => {
    const res = {
      setHeader: vi.fn(),
      flushHeaders: vi.fn(),
      write: vi.fn(),
      on: vi.fn(),
      end: vi.fn(),
    } as unknown as Response;

    mockService.streamNotifications.mockResolvedValue(undefined);

    await controller.streamNotifications(res);

    expect(mockService.streamNotifications).toHaveBeenCalledWith(res);
  });

  it("acks a notification by id", async () => {
    mockService.ackNotification.mockResolvedValue({ success: true, id: "notif-2" });

    await expect(controller.ackNotification("notif-2")).resolves.toEqual({
      success: true,
      id: "notif-2",
    });
  });

  it("surfaces ack errors", async () => {
    mockService.ackNotification.mockRejectedValue(new NotFoundException("missing"));

    await expect(controller.ackNotification("missing")).rejects.toThrow(NotFoundException);
  });

  it("returns parsed queue tasks", async () => {
    mockService.listTasks.mockResolvedValue([
      {
        id: "task-1",
        project: "mosaic-stack",
        taskId: "MS24-API-001",
        status: "pending",
        description: "Build queue notifications module",
      },
    ]);

    await expect(controller.getTasks()).resolves.toEqual([
      {
        id: "task-1",
        project: "mosaic-stack",
        taskId: "MS24-API-001",
        status: "pending",
        description: "Build queue notifications module",
      },
    ]);
  });

  it("uses ApiKeyGuard at the controller level", () => {
    const guards = Reflect.getMetadata("__guards__", QueueNotificationsController) as unknown[];

    expect(guards).toContain(ApiKeyGuard);
  });
});
