/**
 * EventController Tests
 *
 * Tests for event subscription and publishing endpoints.
 */

import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { EventController } from "./event.controller";
import { EventService } from "./event.service";
import { AuthGuard } from "../auth/guards/auth.guard";
import { FederationMessageType, FederationMessageStatus } from "@prisma/client";
import type { AuthenticatedRequest } from "../common/types/user.types";
import type { EventMessage, EventAck } from "./types/message.types";

describe("EventController", () => {
  let controller: EventController;
  let eventService: EventService;

  const mockEventService = {
    subscribeToEventType: vi.fn(),
    unsubscribeFromEventType: vi.fn(),
    publishEvent: vi.fn(),
    getEventSubscriptions: vi.fn(),
    getEventMessages: vi.fn(),
    getEventMessage: vi.fn(),
    handleIncomingEvent: vi.fn(),
    processEventAck: vi.fn(),
  };

  const mockWorkspaceId = "workspace-123";
  const mockUserId = "user-123";
  const mockConnectionId = "connection-123";
  const mockEventType = "task.created";

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

    const module: TestingModule = await Test.createTestingModule({
      controllers: [EventController],
      providers: [
        {
          provide: EventService,
          useValue: mockEventService,
        },
      ],
    })
      .overrideGuard(AuthGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get<EventController>(EventController);
    eventService = module.get<EventService>(EventService);
  });

  describe("subscribeToEvent", () => {
    it("should subscribe to an event type", async () => {
      const req = {
        user: {
          id: mockUserId,
          workspaceId: mockWorkspaceId,
        },
      } as AuthenticatedRequest;

      const dto = {
        connectionId: mockConnectionId,
        eventType: mockEventType,
        metadata: { key: "value" },
      };

      const mockSubscription = {
        id: "sub-123",
        workspaceId: mockWorkspaceId,
        connectionId: mockConnectionId,
        eventType: mockEventType,
        metadata: { key: "value" },
        isActive: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockEventService.subscribeToEventType.mockResolvedValue(mockSubscription);

      const result = await controller.subscribeToEvent(req, dto);

      expect(result).toEqual(mockSubscription);
      expect(mockEventService.subscribeToEventType).toHaveBeenCalledWith(
        mockWorkspaceId,
        mockConnectionId,
        mockEventType,
        { key: "value" }
      );
    });

    it("should throw error if workspace not found", async () => {
      const req = {
        user: {
          id: mockUserId,
        },
      } as AuthenticatedRequest;

      const dto = {
        connectionId: mockConnectionId,
        eventType: mockEventType,
      };

      await expect(controller.subscribeToEvent(req, dto)).rejects.toThrow(
        "Workspace ID not found in request"
      );
    });
  });

  describe("unsubscribeFromEvent", () => {
    it("should unsubscribe from an event type", async () => {
      const req = {
        user: {
          id: mockUserId,
          workspaceId: mockWorkspaceId,
        },
      } as AuthenticatedRequest;

      const dto = {
        connectionId: mockConnectionId,
        eventType: mockEventType,
      };

      mockEventService.unsubscribeFromEventType.mockResolvedValue(undefined);

      await controller.unsubscribeFromEvent(req, dto);

      expect(mockEventService.unsubscribeFromEventType).toHaveBeenCalledWith(
        mockWorkspaceId,
        mockConnectionId,
        mockEventType
      );
    });
  });

  describe("publishEvent", () => {
    it("should publish an event", async () => {
      const req = {
        user: {
          id: mockUserId,
          workspaceId: mockWorkspaceId,
        },
      } as AuthenticatedRequest;

      const dto = {
        eventType: mockEventType,
        payload: { data: "test" },
      };

      const mockMessages = [
        {
          id: "msg-123",
          workspaceId: mockWorkspaceId,
          connectionId: mockConnectionId,
          messageType: FederationMessageType.EVENT,
          messageId: "msg-id-123",
          eventType: mockEventType,
          payload: { data: "test" },
          status: FederationMessageStatus.DELIVERED,
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockEventService.publishEvent.mockResolvedValue(mockMessages);

      const result = await controller.publishEvent(req, dto);

      expect(result).toEqual(mockMessages);
      expect(mockEventService.publishEvent).toHaveBeenCalledWith(mockWorkspaceId, mockEventType, {
        data: "test",
      });
    });
  });

  describe("getSubscriptions", () => {
    it("should return all subscriptions for workspace", async () => {
      const req = {
        user: {
          id: mockUserId,
          workspaceId: mockWorkspaceId,
        },
      } as AuthenticatedRequest;

      const mockSubscriptions = [
        {
          id: "sub-1",
          workspaceId: mockWorkspaceId,
          connectionId: mockConnectionId,
          eventType: "task.created",
          metadata: {},
          isActive: true,
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockEventService.getEventSubscriptions.mockResolvedValue(mockSubscriptions);

      const result = await controller.getSubscriptions(req);

      expect(result).toEqual(mockSubscriptions);
      expect(mockEventService.getEventSubscriptions).toHaveBeenCalledWith(
        mockWorkspaceId,
        undefined
      );
    });

    it("should filter by connectionId when provided", async () => {
      const req = {
        user: {
          id: mockUserId,
          workspaceId: mockWorkspaceId,
        },
      } as AuthenticatedRequest;

      const mockSubscriptions = [
        {
          id: "sub-1",
          workspaceId: mockWorkspaceId,
          connectionId: mockConnectionId,
          eventType: "task.created",
          metadata: {},
          isActive: true,
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockEventService.getEventSubscriptions.mockResolvedValue(mockSubscriptions);

      const result = await controller.getSubscriptions(req, mockConnectionId);

      expect(result).toEqual(mockSubscriptions);
      expect(mockEventService.getEventSubscriptions).toHaveBeenCalledWith(
        mockWorkspaceId,
        mockConnectionId
      );
    });
  });

  describe("getEventMessages", () => {
    it("should return all event messages for workspace", async () => {
      const req = {
        user: {
          id: mockUserId,
          workspaceId: mockWorkspaceId,
        },
      } as AuthenticatedRequest;

      const mockMessages = [
        {
          id: "msg-1",
          workspaceId: mockWorkspaceId,
          connectionId: mockConnectionId,
          messageType: FederationMessageType.EVENT,
          messageId: "msg-id-1",
          eventType: "task.created",
          payload: { data: "test1" },
          status: FederationMessageStatus.DELIVERED,
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockEventService.getEventMessages.mockResolvedValue(mockMessages);

      const result = await controller.getEventMessages(req);

      expect(result).toEqual(mockMessages);
      expect(mockEventService.getEventMessages).toHaveBeenCalledWith(mockWorkspaceId, undefined);
    });

    it("should filter by status when provided", async () => {
      const req = {
        user: {
          id: mockUserId,
          workspaceId: mockWorkspaceId,
        },
      } as AuthenticatedRequest;

      const mockMessages = [
        {
          id: "msg-1",
          workspaceId: mockWorkspaceId,
          connectionId: mockConnectionId,
          messageType: FederationMessageType.EVENT,
          messageId: "msg-id-1",
          eventType: "task.created",
          payload: { data: "test1" },
          status: FederationMessageStatus.PENDING,
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockEventService.getEventMessages.mockResolvedValue(mockMessages);

      const result = await controller.getEventMessages(req, FederationMessageStatus.PENDING);

      expect(result).toEqual(mockMessages);
      expect(mockEventService.getEventMessages).toHaveBeenCalledWith(
        mockWorkspaceId,
        FederationMessageStatus.PENDING
      );
    });
  });

  describe("getEventMessage", () => {
    it("should return a single event message", async () => {
      const req = {
        user: {
          id: mockUserId,
          workspaceId: mockWorkspaceId,
        },
      } as AuthenticatedRequest;

      const messageId = "msg-123";

      const mockMessage = {
        id: messageId,
        workspaceId: mockWorkspaceId,
        connectionId: mockConnectionId,
        messageType: FederationMessageType.EVENT,
        messageId: "msg-id-123",
        eventType: "task.created",
        payload: { data: "test" },
        status: FederationMessageStatus.DELIVERED,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockEventService.getEventMessage.mockResolvedValue(mockMessage);

      const result = await controller.getEventMessage(req, messageId);

      expect(result).toEqual(mockMessage);
      expect(mockEventService.getEventMessage).toHaveBeenCalledWith(mockWorkspaceId, messageId);
    });
  });

  describe("handleIncomingEvent", () => {
    it("should handle incoming event and return acknowledgment", async () => {
      const eventMessage: EventMessage = {
        messageId: "msg-123",
        instanceId: "remote-instance-123",
        eventType: "task.created",
        payload: { data: "test" },
        timestamp: Date.now(),
        signature: "signature-123",
      };

      const mockAck: EventAck = {
        messageId: "ack-123",
        correlationId: eventMessage.messageId,
        instanceId: "local-instance-123",
        received: true,
        timestamp: Date.now(),
        signature: "ack-signature-123",
      };

      mockEventService.handleIncomingEvent.mockResolvedValue(mockAck);

      const result = await controller.handleIncomingEvent(eventMessage);

      expect(result).toEqual(mockAck);
      expect(mockEventService.handleIncomingEvent).toHaveBeenCalledWith(eventMessage);
    });
  });

  describe("handleIncomingEventAck", () => {
    it("should process event acknowledgment", async () => {
      const ack: EventAck = {
        messageId: "ack-123",
        correlationId: "msg-123",
        instanceId: "remote-instance-123",
        received: true,
        timestamp: Date.now(),
        signature: "ack-signature-123",
      };

      mockEventService.processEventAck.mockResolvedValue(undefined);

      const result = await controller.handleIncomingEventAck(ack);

      expect(result).toEqual({ status: "acknowledged" });
      expect(mockEventService.processEventAck).toHaveBeenCalledWith(ack);
    });
  });
});
